mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-07 02:38:11 +08:00
merge typescript changes
This commit is contained in:
@@ -28,8 +28,8 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7] # As of Oct 2020 Anndata is not compatible with 3.8
|
python-version: [3.6, 3.7, 3.8]
|
||||||
anndata-version: [0.7.0, 0.7.1, 0.7.2, 0.7.3, 0.7.4, 0.7.5]
|
anndata-version: [0.7.6]
|
||||||
test-suite: [smoke-test, smoke-test-annotations]
|
test-suite: [smoke-test, smoke-test-annotations]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -47,8 +47,6 @@ jobs:
|
|||||||
make pydist install-dist
|
make pydist install-dist
|
||||||
# 3. install anndata
|
# 3. install anndata
|
||||||
pip install anndata==${{ matrix.anndata-version }}
|
pip install anndata==${{ matrix.anndata-version }}
|
||||||
# workaround for anndata 0.6.22.post1 bug
|
|
||||||
[[ "0.6.22.post1" = "${{ matrix.anndata-version }}" ]] && pip install h5py==2.9.0 || true
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: make unit-test ${{ matrix.test-suite }}
|
run: make unit-test ${{ matrix.test-suite }}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Lint src with eslint
|
- name: Lint src with eslint
|
||||||
working-directory: ./client
|
working-directory: ./client
|
||||||
run: |
|
run: |
|
||||||
make lint
|
npx eslint src __tests__
|
||||||
|
|
||||||
unit-test:
|
unit-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -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 }}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy import sparse, stats
|
from scipy import sparse, stats
|
||||||
from backend.common.constants import XApproxDistribution
|
from backend.common.constants import XApproximateDistribution
|
||||||
|
|
||||||
|
|
||||||
def diffexp_ttest(adaptor, maskA, maskB, top_n=8, diffexp_lfc_cutoff=0.01):
|
def diffexp_ttest(adaptor, maskA, maskB, top_n=8, diffexp_lfc_cutoff=0.01):
|
||||||
@@ -30,13 +30,13 @@ def diffexp_ttest(adaptor, maskA, maskB, top_n=8, diffexp_lfc_cutoff=0.01):
|
|||||||
:return: for top N genes, {"positive": for top N genes, [ varindex, foldchange, pval, pval_adj ], "negative": for top N genes, [ varindex, foldchange, pval, pval_adj ]}
|
:return: for top N genes, {"positive": for top N genes, [ varindex, foldchange, pval, pval_adj ], "negative": for top N genes, [ varindex, foldchange, pval, pval_adj ]}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
X_approx_distribution = adaptor.get_X_approx_distribution()
|
X_approximate_distribution = adaptor.get_X_approximate_distribution()
|
||||||
dataA = adaptor.get_X_array(maskA, None)
|
dataA = adaptor.get_X_array(maskA, None)
|
||||||
dataB = adaptor.get_X_array(maskB, None)
|
dataB = adaptor.get_X_array(maskB, None)
|
||||||
|
|
||||||
# mean, variance, N - calculate for both selections
|
# mean, variance, N - calculate for both selections
|
||||||
meanA, vA, nA = mean_var_n(dataA, X_approx_distribution)
|
meanA, vA, nA = mean_var_n(dataA, X_approximate_distribution)
|
||||||
meanB, vB, nB = mean_var_n(dataB, X_approx_distribution)
|
meanB, vB, nB = mean_var_n(dataB, X_approximate_distribution)
|
||||||
res = diffexp_ttest_from_mean_var(meanA, vA, nA, meanB, vB, nB, top_n, diffexp_lfc_cutoff)
|
res = diffexp_ttest_from_mean_var(meanA, vA, nA, meanB, vB, nB, top_n, diffexp_lfc_cutoff)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -113,7 +113,7 @@ def diffexp_ttest_from_mean_var(meanA, varA, nA, meanB, varB, nB, top_n, diffexp
|
|||||||
|
|
||||||
|
|
||||||
# Convenience function which handles sparse data
|
# Convenience function which handles sparse data
|
||||||
def mean_var_n(X, X_approx_distribution=XApproxDistribution.NORMAL):
|
def mean_var_n(X, X_approximate_distribution=XApproximateDistribution.NORMAL):
|
||||||
"""
|
"""
|
||||||
Two-pass variance calculation. Numerically (more) stable
|
Two-pass variance calculation. Numerically (more) stable
|
||||||
than naive methods (and same method used by numpy.var())
|
than naive methods (and same method used by numpy.var())
|
||||||
@@ -131,14 +131,14 @@ def mean_var_n(X, X_approx_distribution=XApproxDistribution.NORMAL):
|
|||||||
with np.errstate(divide="call", invalid="call", call=fp_err_set):
|
with np.errstate(divide="call", invalid="call", call=fp_err_set):
|
||||||
n = X.shape[0]
|
n = X.shape[0]
|
||||||
if sparse.issparse(X):
|
if sparse.issparse(X):
|
||||||
if X_approx_distribution == XApproxDistribution.COUNT:
|
if X_approximate_distribution == XApproximateDistribution.COUNT:
|
||||||
X = X.log1p()
|
X = X.log1p()
|
||||||
mean = X.mean(axis=0).A1
|
mean = X.mean(axis=0).A1
|
||||||
dfm = X - mean
|
dfm = X - mean
|
||||||
sumsq = np.sum(np.multiply(dfm, dfm), axis=0).A1
|
sumsq = np.sum(np.multiply(dfm, dfm), axis=0).A1
|
||||||
v = sumsq / (n - 1)
|
v = sumsq / (n - 1)
|
||||||
else:
|
else:
|
||||||
if X_approx_distribution == XApproxDistribution.COUNT:
|
if X_approximate_distribution == XApproximateDistribution.COUNT:
|
||||||
X = np.log1p(X)
|
X = np.log1p(X)
|
||||||
mean = X.mean(axis=0)
|
mean = X.mean(axis=0)
|
||||||
dfm = X - mean
|
dfm = X - mean
|
||||||
|
|||||||
@@ -2,34 +2,52 @@ import numba
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy import sparse
|
from scipy import sparse
|
||||||
from backend.common.constants import XApproxDistribution
|
from backend.common.constants import XApproximateDistribution
|
||||||
|
|
||||||
|
|
||||||
@numba.njit(fastmath=True, error_model="numpy", nogil=True)
|
@numba.njit(error_model="numpy", nogil=True)
|
||||||
def min_max(arr):
|
def min_max(arr: np.ndarray):
|
||||||
"""Return (min, max) values for the ndarray."""
|
"""Return (min, max) values for the ndarray."""
|
||||||
n = arr.size
|
|
||||||
odd = n % 2
|
# initialize to first finite value in array. Normally,
|
||||||
if not odd:
|
# this will exit on the first value.
|
||||||
n -= 1
|
for i in range(arr.size):
|
||||||
max_val = min_val = arr[0]
|
min_val = max_val = arr[i]
|
||||||
i = 1
|
if np.isfinite(min_val):
|
||||||
while i < n:
|
break
|
||||||
|
|
||||||
|
# now find min/max, unrolled by two
|
||||||
|
odd = arr.size % 2
|
||||||
|
unrolled_loop_limit = arr.size - 1 if odd else arr.size
|
||||||
|
i = 0
|
||||||
|
while i < unrolled_loop_limit:
|
||||||
x = arr[i]
|
x = arr[i]
|
||||||
y = arr[i + 1]
|
y = arr[i + 1]
|
||||||
|
|
||||||
|
# ignore non-finites
|
||||||
|
x = x if np.isfinite(x) else min_val
|
||||||
|
y = y if np.isfinite(y) else min_val
|
||||||
|
|
||||||
if x > y:
|
if x > y:
|
||||||
x, y = y, x
|
x, y = y, x
|
||||||
min_val = min(x, min_val)
|
min_val = min(x, min_val)
|
||||||
max_val = max(y, max_val)
|
max_val = max(y, max_val)
|
||||||
i += 2
|
i += 2
|
||||||
if not odd:
|
|
||||||
x = arr[n]
|
# handle the tail if any
|
||||||
|
if odd:
|
||||||
|
x = arr[arr.size - 1]
|
||||||
|
|
||||||
|
# ignore non-finites
|
||||||
|
x = x if np.isfinite(x) else min_val
|
||||||
|
|
||||||
min_val = min(x, min_val)
|
min_val = min(x, min_val)
|
||||||
max_val = max(x, max_val)
|
max_val = max(x, max_val)
|
||||||
|
|
||||||
return min_val, max_val
|
return min_val, max_val
|
||||||
|
|
||||||
|
|
||||||
def estimate_approximate_distribution(X) -> XApproxDistribution:
|
def estimate_approximate_distribution(X) -> XApproximateDistribution:
|
||||||
"""
|
"""
|
||||||
Estimate the distribution (normal, count) of the X matrix.
|
Estimate the distribution (normal, count) of the X matrix.
|
||||||
|
|
||||||
@@ -38,6 +56,13 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
|
|||||||
any (max-min) range in excess of 24 is implies tens of millions of
|
any (max-min) range in excess of 24 is implies tens of millions of
|
||||||
observations of a single feature and so is extremely unlikely.
|
observations of a single feature and so is extremely unlikely.
|
||||||
"""
|
"""
|
||||||
|
if X.dtype.kind not in ["i", "u", "f"]:
|
||||||
|
raise TypeError(f"Unsupported matrix dtype: {X.dtype.name}")
|
||||||
|
|
||||||
|
if X.size == 0:
|
||||||
|
# default for empty array
|
||||||
|
return XApproximateDistribution.NORMAL
|
||||||
|
|
||||||
if sparse.isspmatrix_csc(X) or sparse.isspmatrix_csr(X):
|
if sparse.isspmatrix_csc(X) or sparse.isspmatrix_csr(X):
|
||||||
Xdata = X.data
|
Xdata = X.data
|
||||||
elif type(X) is np.ndarray:
|
elif type(X) is np.ndarray:
|
||||||
@@ -45,7 +70,7 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
|
|||||||
X.size,
|
X.size,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Unsupported matrix type: {str(type(X))}")
|
raise TypeError(f"Unsupported matrix format: {str(type(X))}")
|
||||||
|
|
||||||
CHUNKSIZE = 1 << 24
|
CHUNKSIZE = 1 << 24
|
||||||
if Xdata.size > CHUNKSIZE:
|
if Xdata.size > CHUNKSIZE:
|
||||||
@@ -59,4 +84,4 @@ def estimate_approximate_distribution(X) -> XApproxDistribution:
|
|||||||
min_val, max_val = min_max(Xdata)
|
min_val, max_val = min_max(Xdata)
|
||||||
|
|
||||||
excess_range = (max_val - min_val) > 24
|
excess_range = (max_val - min_val) > 24
|
||||||
return XApproxDistribution.COUNT if excess_range else XApproxDistribution.NORMAL
|
return XApproximateDistribution.COUNT if excess_range else XApproximateDistribution.NORMAL
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class DiffExpMode(AugmentedEnum):
|
|||||||
VAR_FILTER = "varFilter"
|
VAR_FILTER = "varFilter"
|
||||||
|
|
||||||
|
|
||||||
class XApproxDistribution(AugmentedEnum):
|
class XApproximateDistribution(AugmentedEnum):
|
||||||
NORMAL = "normal"
|
NORMAL = "normal"
|
||||||
COUNT = "count"
|
COUNT = "count"
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import pandas as pd
|
|||||||
from flatbuffers import Builder
|
from flatbuffers import Builder
|
||||||
from scipy import sparse
|
from scipy import sparse
|
||||||
|
|
||||||
|
from backend.common.utils.type_conversion_utils import get_encoding_dtype_of_array
|
||||||
|
|
||||||
import backend.common.fbs.NetEncoding.Column as Column
|
import backend.common.fbs.NetEncoding.Column as Column
|
||||||
import backend.common.fbs.NetEncoding.Float32Array as Float32Array
|
import backend.common.fbs.NetEncoding.Float32Array as Float32Array
|
||||||
import backend.common.fbs.NetEncoding.Float64Array as Float64Array
|
import backend.common.fbs.NetEncoding.Float64Array as Float64Array
|
||||||
@@ -14,6 +16,7 @@ import backend.common.fbs.NetEncoding.Matrix as Matrix
|
|||||||
import backend.common.fbs.NetEncoding.TypedArray as TypedArray
|
import backend.common.fbs.NetEncoding.TypedArray as TypedArray
|
||||||
import backend.common.fbs.NetEncoding.Uint32Array as Uint32Array
|
import backend.common.fbs.NetEncoding.Uint32Array as Uint32Array
|
||||||
|
|
||||||
|
|
||||||
# Serialization helper
|
# Serialization helper
|
||||||
def serialize_column(builder, typed_arr):
|
def serialize_column(builder, typed_arr):
|
||||||
""" Serialize NetEncoding.Column """
|
""" Serialize NetEncoding.Column """
|
||||||
@@ -84,7 +87,7 @@ def serialize_typed_array(builder, source_array, encoding_info):
|
|||||||
def column_encoding(arr):
|
def column_encoding(arr):
|
||||||
column_encoding_type_map = {
|
column_encoding_type_map = {
|
||||||
# array protocol string: ( array_type, as_type )
|
# array protocol string: ( array_type, as_type )
|
||||||
np.dtype(np.float64).str: (TypedArray.TypedArray.Float64Array, np.float64),
|
np.dtype(np.float64).str: (TypedArray.TypedArray.Float32Array, np.float32),
|
||||||
np.dtype(np.float32).str: (TypedArray.TypedArray.Float32Array, np.float32),
|
np.dtype(np.float32).str: (TypedArray.TypedArray.Float32Array, np.float32),
|
||||||
np.dtype(np.float16).str: (TypedArray.TypedArray.Float32Array, np.float32),
|
np.dtype(np.float16).str: (TypedArray.TypedArray.Float32Array, np.float32),
|
||||||
np.dtype(np.int8).str: (TypedArray.TypedArray.Int32Array, np.int32),
|
np.dtype(np.int8).str: (TypedArray.TypedArray.Int32Array, np.int32),
|
||||||
@@ -98,7 +101,8 @@ def column_encoding(arr):
|
|||||||
}
|
}
|
||||||
column_encoding_default = (TypedArray.TypedArray.JSONEncodedArray, "json")
|
column_encoding_default = (TypedArray.TypedArray.JSONEncodedArray, "json")
|
||||||
|
|
||||||
return column_encoding_type_map.get(arr.dtype.str, column_encoding_default)
|
encoding_dtype = np.dtype(get_encoding_dtype_of_array(arr))
|
||||||
|
return column_encoding_type_map.get(encoding_dtype.str, column_encoding_default)
|
||||||
|
|
||||||
|
|
||||||
def index_encoding(arr):
|
def index_encoding(arr):
|
||||||
@@ -198,7 +202,7 @@ def deserialize_typed_array(tarr):
|
|||||||
arr.Init(u.Bytes, u.Pos)
|
arr.Init(u.Bytes, u.Pos)
|
||||||
narr = arr.DataAsNumpy()
|
narr = arr.DataAsNumpy()
|
||||||
if u_type == TypedArray.TypedArray.JSONEncodedArray:
|
if u_type == TypedArray.TypedArray.JSONEncodedArray:
|
||||||
narr = json.loads(narr.tostring().decode("utf-8"))
|
narr = json.loads(narr.tobytes().decode("utf-8"))
|
||||||
return narr
|
return narr
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,42 @@
|
|||||||
|
from typing import Union, Tuple
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
"""
|
||||||
|
These routines drive all type inference for the schema generation and the
|
||||||
|
FBS (REST OTA) encoding. They are also used for CXG generation.
|
||||||
|
|
||||||
|
|
||||||
|
H5AD Type REST REST
|
||||||
|
(ndarray, Series, Index) FBS encoding schema type ERROR/exceptions
|
||||||
|
---------------------------- -------------- --------------- ----------------------
|
||||||
|
bool_/bool uint8 boolean
|
||||||
|
(u)int8, (u)int16, int32 int32 int32
|
||||||
|
uint32, (u)int64 int32 int32 CHECKS value bounds
|
||||||
|
float16, float32, float64 float32 float32[0]
|
||||||
|
|
||||||
|
categorical[T is numeric[4]]:
|
||||||
|
hasna = False T categorical[1]
|
||||||
|
hasna = True float32 categorical[1] CHECKS value bounds
|
||||||
|
|
||||||
|
categorical[T not numeric] JSON/str categorical[1,2]
|
||||||
|
|
||||||
|
(other object) JSON/str string
|
||||||
|
|
||||||
|
(all other) Always an ERROR[3]
|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
[0] IEEE format, includes non-finite numbers (NaN, Inf, ...)
|
||||||
|
[1] with NO categories enumerated (client side does it to handle rounding)
|
||||||
|
[2] NA (undefined) categories are assigned a JSON null value
|
||||||
|
[3] Includes all other numpy types: datetime, complex, etc.
|
||||||
|
[4] means float, int, uint (dtype.kind in ['i','u','f'])
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_dtypes_and_schemas_of_dataframe(dataframe: pd.DataFrame):
|
def get_dtypes_and_schemas_of_dataframe(dataframe: pd.DataFrame):
|
||||||
dtypes_by_column_name = {}
|
dtypes_by_column_name = {}
|
||||||
@@ -17,133 +51,132 @@ def get_dtypes_and_schemas_of_dataframe(dataframe: pd.DataFrame):
|
|||||||
return dtypes_by_column_name, schema_type_hints_by_column_name
|
return dtypes_by_column_name, schema_type_hints_by_column_name
|
||||||
|
|
||||||
|
|
||||||
def get_dtype_of_array(array: pd.Series):
|
def get_encoding_dtype_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> np.dtype:
|
||||||
return get_dtype_and_schema_of_array(array)[0]
|
return _get_type_info(array)[0]
|
||||||
|
|
||||||
|
|
||||||
def get_schema_type_hint_of_array(array: pd.Series):
|
def get_schema_type_hint_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> dict:
|
||||||
return get_dtype_and_schema_of_array(array)[1]
|
return _get_type_info(array)[1]
|
||||||
|
|
||||||
|
|
||||||
def get_dtype_and_schema_of_array(array: pd.Series):
|
def get_dtype_and_schema_of_array(array: Union[np.ndarray, pd.Series, pd.Index]) -> Tuple[np.dtype, dict]:
|
||||||
return (
|
"""Return tuple (encoding_dtype, schema_type_hint)"""
|
||||||
get_dtype_from_dtype(array.dtype, array_values=array),
|
return _get_type_info(array)
|
||||||
get_schema_type_hint_from_dtype(array.dtype, array_values=array),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dtype_from_dtype(dtype, array_values=None):
|
def get_schema_type_hint_from_dtype(dtype) -> dict:
|
||||||
|
res = _get_type_info_from_dtype(dtype)
|
||||||
|
if res is None:
|
||||||
|
raise TypeError(f"Annotations of type {dtype} are unsupported.")
|
||||||
|
else:
|
||||||
|
return res[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_type_info_from_dtype(dtype) -> Union[Tuple[np.dtype, dict], None]:
|
||||||
"""
|
"""
|
||||||
Given a data type, finds the equivalent data type that the array should be encoded as. Notably, this is relevant
|
Best-effort to determine encoding type and schema hint from a dtype.
|
||||||
for 64 bit values which will get downcast to 32 bit.
|
If this is not possible, or the type is unsupported, return None.
|
||||||
|
|
||||||
|
This should be a subset of the cases which are supported by
|
||||||
|
_get_type_info(). The latter should be preferred if the array (values)
|
||||||
|
are available for typing.
|
||||||
"""
|
"""
|
||||||
|
if dtype.kind == "b":
|
||||||
|
return (np.uint8, {"type": "boolean"})
|
||||||
|
|
||||||
dtype_name = dtype.name
|
if dtype.kind == "U":
|
||||||
dtype_kind = dtype.kind
|
return (np.dtype(str), {"type": "string"})
|
||||||
|
|
||||||
if dtype_name == "bool":
|
|
||||||
return np.uint8
|
|
||||||
if dtype_name == "object" and dtype_kind == "O":
|
|
||||||
return str
|
|
||||||
if dtype_name == "category":
|
|
||||||
return get_dtype_from_dtype(dtype.categories.dtype, array_values)
|
|
||||||
|
|
||||||
if can_cast_to_int32(dtype, array_values):
|
|
||||||
return np.int32
|
|
||||||
if can_cast_to_float32(dtype, array_values):
|
|
||||||
return np.float32
|
|
||||||
if not can_cast_to_float32(dtype, array_values):
|
|
||||||
return np.float64
|
|
||||||
|
|
||||||
raise TypeError(f"Annotations of type {dtype} are unsupported.")
|
|
||||||
|
|
||||||
|
|
||||||
def get_schema_type_hint_from_dtype(dtype, array_values=None):
|
|
||||||
"""
|
|
||||||
Returns a dictionary that contains type hints about the data type given, especially if the data type is 64 bit
|
|
||||||
and will be downcast to 32 bit.
|
|
||||||
"""
|
|
||||||
|
|
||||||
dtype_name = dtype.name
|
|
||||||
dtype_kind = dtype.kind
|
|
||||||
|
|
||||||
if dtype == np.float32 or dtype == np.int32:
|
|
||||||
return {"type": dtype_name}
|
|
||||||
if dtype_name == "bool":
|
|
||||||
return {"type": "boolean"}
|
|
||||||
if dtype_name == "object" and dtype_kind == "O":
|
|
||||||
return {"type": "string"}
|
|
||||||
if dtype_name == "category":
|
|
||||||
return {"type": "categorical", "categories": dtype.categories.tolist()}
|
|
||||||
|
|
||||||
if can_cast_to_int32(dtype, array_values):
|
|
||||||
return {"type": "int32"}
|
|
||||||
if can_cast_to_float32(dtype, array_values):
|
|
||||||
return {"type": "float32"}
|
|
||||||
if dtype_kind == "f" and not can_cast_to_float32(dtype, array_values):
|
|
||||||
return {"type": "float64"}
|
|
||||||
|
|
||||||
raise TypeError(f"Annotations of type {dtype} are unsupported.")
|
|
||||||
|
|
||||||
|
|
||||||
def can_cast_to_float32(dtype, array_values):
|
|
||||||
"""
|
|
||||||
Optimistically returns True signifying that a type downcast to float32 is possible whenever the incoming type is
|
|
||||||
a float.
|
|
||||||
|
|
||||||
We also handle a special case here where the array is a Series object with integer categorical values AND NaNs.
|
|
||||||
Since NaNs are floating points in numpy, we upcast the integer array to float32 and return True.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if dtype.kind == "f":
|
|
||||||
if not np.can_cast(dtype, np.float32):
|
|
||||||
logging.warning(f"Type {dtype.name} will be converted to 32 bit float and may lose precision.")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
if dtype.kind == "O" and array_values.hasnans:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def can_cast_to_int32(dtype, array_values=None):
|
|
||||||
"""
|
|
||||||
A type can be cast to 32 bit, overriding the numpy `cast_cast` function if the values in the array that are of
|
|
||||||
the higher precision type has values that are entirely within the range of the downcast type.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Since a NaN is technically a float, any array that contains NaNs cannot be cast to an integer so immediately
|
|
||||||
# return False.
|
|
||||||
if array_values.hasnans:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# If the array is categorical, then we need to order the array values so that functions min and max that occur
|
|
||||||
# later, can function. They do not function on unordered categories.
|
|
||||||
ordered_array_values = array_values
|
|
||||||
if array_values.dtype.name == "category" and not array_values.cat.ordered:
|
|
||||||
ordered_array_values = array_values.cat.as_ordered()
|
|
||||||
|
|
||||||
if dtype.kind in ["i", "u"]:
|
if dtype.kind in ["i", "u"]:
|
||||||
if np.can_cast(dtype, np.int32):
|
if np.can_cast(dtype, np.int32):
|
||||||
return True
|
return (np.int32, {"type": "int32"})
|
||||||
ii32 = np.iinfo(np.int32)
|
|
||||||
if (
|
if dtype.kind == "f":
|
||||||
not ordered_array_values.empty
|
_float64_warning(dtype)
|
||||||
and (ordered_array_values.min() >= ii32.min and ordered_array_values.max() <= ii32.max)
|
return (np.float32, {"type": "float32"})
|
||||||
or ordered_array_values.empty
|
|
||||||
):
|
if dtype.kind == "O" and not dtype.name == "category":
|
||||||
return True
|
return (np.dtype(str), {"type": "string"})
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_type_info(array: Union[np.ndarray, pd.Series, pd.Index]) -> Tuple[np.dtype, dict]:
|
||||||
|
"""
|
||||||
|
Determine encoding type and schema hint from an array. This allows more
|
||||||
|
flexible casting than may be possible by using just the dtype, as it can
|
||||||
|
account for category types and array values.
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
not isinstance(array, np.ndarray)
|
||||||
|
and not isinstance(array, pd.Series)
|
||||||
|
and not isinstance(array, pd.Index)
|
||||||
|
and not hasattr(array, "dtype")
|
||||||
|
):
|
||||||
|
raise TypeError("Unsupported data type.")
|
||||||
|
|
||||||
|
dtype = array.dtype
|
||||||
|
|
||||||
|
res = _get_type_info_from_dtype(dtype)
|
||||||
|
if res is not None:
|
||||||
|
return res
|
||||||
|
|
||||||
|
if dtype.kind == "O":
|
||||||
|
if dtype.name == "category":
|
||||||
|
# Sometimes CategoricalDType can be encoded as int or float without further fuss.
|
||||||
|
# Do not specify the categories in the schema - let the client-side figure it out
|
||||||
|
# on its own. Utilize Series.to_numpy() to do casting that handles categorical
|
||||||
|
# NA/NaN (missing or undefined) categories.
|
||||||
|
if dtype.categories.dtype.kind in ["f", "i", "u"]:
|
||||||
|
return (
|
||||||
|
_get_type_info(array.to_numpy())[0],
|
||||||
|
{"type": "categorical"},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return (np.dtype(str), {"type": "categorical", "categories": dtype.categories.to_list()})
|
||||||
|
|
||||||
|
# all other extension types are str-encoded
|
||||||
|
return (np.dtype(str), {"type": "string"})
|
||||||
|
|
||||||
|
if dtype.kind in ["i", "u"] and _can_cast_array_values_to_int32(array):
|
||||||
|
return (np.int32, {"type": "int32"})
|
||||||
|
|
||||||
|
if dtype.kind == "f":
|
||||||
|
_float64_warning(array.dtype)
|
||||||
|
return (np.float32, {"type": "float32"})
|
||||||
|
|
||||||
|
raise TypeError(f"Annotations of type {dtype} are unsupported.")
|
||||||
|
|
||||||
|
|
||||||
|
def _float64_warning(dtype):
|
||||||
|
"""
|
||||||
|
Warn the user if we are down-casting a float64 to float32, and may potentially lose information.
|
||||||
|
"""
|
||||||
|
if dtype.kind == "f" and not np.can_cast(dtype, np.float32):
|
||||||
|
logging.warning(f"Type {dtype.name} will be converted to 32 bit float and may lose precision.")
|
||||||
|
|
||||||
|
|
||||||
|
def _can_cast_array_values_to_int32(array: Union[np.ndarray, pd.Series, pd.Index]) -> bool:
|
||||||
|
"""
|
||||||
|
Return true if the (U)INT array values can be safely cast to int32. We allow size reducing
|
||||||
|
casts (ie, int64 to int32) if no actual values require the larger size (ie, actual values
|
||||||
|
can be represented by the smaller type).
|
||||||
|
"""
|
||||||
|
assert array.dtype.kind in ["u", "i"]
|
||||||
|
|
||||||
|
if np.can_cast(array.dtype, np.int32):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if array.size == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
int32_machine_limits = np.iinfo(np.int32)
|
||||||
|
if array.min() >= int32_machine_limits.min and array.max() <= int32_machine_limits.max:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def convert_pandas_series_to_numpy(series_to_convert: pd.Series, dtype):
|
|
||||||
if series_to_convert.hasnans and dtype == np.int32:
|
|
||||||
logging.error("Cannot convert a pandas Series object to an integer dtype if it contains NaNs.")
|
|
||||||
|
|
||||||
return series_to_convert.to_numpy(dtype)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_string_to_value(value: str):
|
def convert_string_to_value(value: str):
|
||||||
"""convert a string to value with the most appropriate type"""
|
"""convert a string to value with the most appropriate type"""
|
||||||
if value.lower() == "true":
|
if value.lower() == "true":
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from flask import current_app
|
|||||||
from backend.czi_hosted.common.annotations.annotations import Annotations
|
from backend.czi_hosted.common.annotations.annotations import Annotations
|
||||||
from backend.common.errors import AnnotationCategoryNameError
|
from backend.common.errors import AnnotationCategoryNameError
|
||||||
from backend.czi_hosted.common.utils.sanitization_utils import sanitize_values_in_list
|
from backend.czi_hosted.common.utils.sanitization_utils import sanitize_values_in_list
|
||||||
from backend.common.utils.type_conversion_utils import get_dtypes_and_schemas_of_dataframe, get_dtype_of_array
|
from backend.common.utils.type_conversion_utils import get_dtypes_and_schemas_of_dataframe, get_encoding_dtype_of_array
|
||||||
from backend.czi_hosted.db.cellxgene_orm import Annotation
|
from backend.czi_hosted.db.cellxgene_orm import Annotation
|
||||||
|
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ class AnnotationsHostedTileDB(Annotations):
|
|||||||
# convert to tiledb datatypes
|
# convert to tiledb datatypes
|
||||||
|
|
||||||
for col in df:
|
for col in df:
|
||||||
df[col] = df[col].astype(get_dtype_of_array(df[col]))
|
df[col] = df[col].astype(get_encoding_dtype_of_array(df[col]))
|
||||||
tiledb.from_pandas(uri, df, sparse=True)
|
tiledb.from_pandas(uri, df, sparse=True)
|
||||||
else:
|
else:
|
||||||
uri = ""
|
uri = ""
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class DatasetConfig(BaseConfig):
|
|||||||
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
|
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
|
||||||
self.diffexp__top_n = default_config["diffexp"]["top_n"]
|
self.diffexp__top_n = default_config["diffexp"]["top_n"]
|
||||||
|
|
||||||
self.X_approx_distribution = default_config["X_approx_distribution"]
|
self.X_approximate_distribution = default_config["X_approximate_distribution"]
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise ConfigurationError(f"Unexpected config: {str(e)}")
|
raise ConfigurationError(f"Unexpected config: {str(e)}")
|
||||||
@@ -60,7 +60,7 @@ class DatasetConfig(BaseConfig):
|
|||||||
self.handle_user_annotations(context)
|
self.handle_user_annotations(context)
|
||||||
self.handle_embeddings()
|
self.handle_embeddings()
|
||||||
self.handle_diffexp(context)
|
self.handle_diffexp(context)
|
||||||
self.handle_X_approx_distribution()
|
self.handle_X_approximate_distribution()
|
||||||
|
|
||||||
def handle_app(self):
|
def handle_app(self):
|
||||||
self.validate_correct_type_of_configuration_attribute("app__scripts", list)
|
self.validate_correct_type_of_configuration_attribute("app__scripts", list)
|
||||||
@@ -203,9 +203,9 @@ class DatasetConfig(BaseConfig):
|
|||||||
"running differential expression may take longer or fail."
|
"running differential expression may take longer or fail."
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_X_approx_distribution(self):
|
def handle_X_approximate_distribution(self):
|
||||||
self.validate_correct_type_of_configuration_attribute("X_approx_distribution", str)
|
self.validate_correct_type_of_configuration_attribute("X_approximate_distribution", str)
|
||||||
if self.X_approx_distribution not in ["normal", "count"]:
|
if self.X_approximate_distribution not in ["normal", "count"]:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
"X_approx_distribution has unknown value -- must be 'normal' or 'count'."
|
"X_approximate_distribution has unknown value -- must be 'normal' or 'count'."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import json
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import tiledb
|
import tiledb
|
||||||
|
|
||||||
from backend.common.utils.type_conversion_utils import get_dtype_of_array, get_dtype_and_schema_of_array
|
from backend.common.utils.type_conversion_utils import get_encoding_dtype_of_array, get_dtype_and_schema_of_array
|
||||||
|
|
||||||
|
|
||||||
def convert_dictionary_to_cxg_group(cxg_container, metadata_dict, group_metadata_name="cxg_group_metadata"):
|
def convert_dictionary_to_cxg_group(cxg_container, metadata_dict, group_metadata_name="cxg_group_metadata"):
|
||||||
@@ -47,7 +47,7 @@ def convert_dataframe_to_cxg_array(cxg_container, dataframe_name, dataframe, ind
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
attrs = [
|
attrs = [
|
||||||
tiledb.Attr(name=column, dtype=get_dtype_of_array(dataframe[column]), filters=tiledb_filter)
|
tiledb.Attr(name=column, dtype=get_encoding_dtype_of_array(dataframe[column]), filters=tiledb_filter)
|
||||||
for column in dataframe
|
for column in dataframe
|
||||||
]
|
]
|
||||||
domain = tiledb.Domain(
|
domain = tiledb.Domain(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from scipy import sparse
|
|||||||
|
|
||||||
import backend.common.compute.diffexp_generic as diffexp_generic
|
import backend.common.compute.diffexp_generic as diffexp_generic
|
||||||
from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors
|
from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors
|
||||||
from backend.common.constants import Axis, MAX_LAYOUTS, XApproxDistribution
|
from backend.common.constants import Axis, MAX_LAYOUTS, XApproximateDistribution
|
||||||
from backend.czi_hosted.common.corpora import corpora_get_props_from_anndata
|
from backend.czi_hosted.common.corpora import corpora_get_props_from_anndata
|
||||||
from backend.common.errors import PrepareError, DatasetAccessError, ConfigurationError
|
from backend.common.errors import PrepareError, DatasetAccessError, ConfigurationError
|
||||||
from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array
|
from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array
|
||||||
@@ -28,7 +28,7 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
def __init__(self, data_locator, app_config=None, dataset_config=None):
|
def __init__(self, data_locator, app_config=None, dataset_config=None):
|
||||||
super().__init__(data_locator, app_config, dataset_config)
|
super().__init__(data_locator, app_config, dataset_config)
|
||||||
self.data = None
|
self.data = None
|
||||||
self.X_approx_distribution = None
|
self.X_approximate_distribution = None
|
||||||
self._load_data(data_locator)
|
self._load_data(data_locator)
|
||||||
self._validate_and_initialize()
|
self._validate_and_initialize()
|
||||||
|
|
||||||
@@ -66,11 +66,11 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_unique_column_name(df, col_name_prefix):
|
def _create_unique_column_name(df, col_name_prefix):
|
||||||
""" given the columns of a dataframe, and a name prefix, return a column name which
|
"""given the columns of a dataframe, and a name prefix, return a column name which
|
||||||
does not exist in the dataframe, AND which is prefixed by `prefix`
|
does not exist in the dataframe, AND which is prefixed by `prefix`
|
||||||
|
|
||||||
The approach is to append a numeric suffix, starting at zero and increasing by
|
The approach is to append a numeric suffix, starting at zero and increasing by
|
||||||
one, until an unused name is found (eg, prefix_0, prefix_1, ...).
|
one, until an unused name is found (eg, prefix_0, prefix_1, ...).
|
||||||
"""
|
"""
|
||||||
suffix = 0
|
suffix = 0
|
||||||
while f"{col_name_prefix}{suffix}" in df:
|
while f"{col_name_prefix}{suffix}" in df:
|
||||||
@@ -124,7 +124,11 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
|
|
||||||
def _create_schema(self):
|
def _create_schema(self):
|
||||||
self.schema = {
|
self.schema = {
|
||||||
"dataframe": {"nObs": self.cell_count, "nVar": self.gene_count, "type": str(self.data.X.dtype)},
|
"dataframe": {
|
||||||
|
"nObs": self.cell_count,
|
||||||
|
"nVar": self.gene_count,
|
||||||
|
**get_schema_type_hint_of_array(self.data.X),
|
||||||
|
},
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"obs": {"index": self.parameters.get("obs_names"), "columns": []},
|
"obs": {"index": self.parameters.get("obs_names"), "columns": []},
|
||||||
"var": {"index": self.parameters.get("var_names"), "columns": []},
|
"var": {"index": self.parameters.get("var_names"), "columns": []},
|
||||||
@@ -191,9 +195,9 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
self.gene_count = self.data.shape[1]
|
self.gene_count = self.data.shape[1]
|
||||||
self._create_schema()
|
self._create_schema()
|
||||||
|
|
||||||
if self.dataset_config.X_approx_distribution == "auto":
|
if self.dataset_config.X_approximate_distribution == "auto":
|
||||||
raise ConfigurationError("X-approx-distribution 'auto' mode unsupported.")
|
raise ConfigurationError("X-approximate-distribution 'auto' mode unsupported.")
|
||||||
self.X_approx_distribution = self.dataset_config.X_approx_distribution
|
self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
|
||||||
|
|
||||||
# heuristic
|
# heuristic
|
||||||
n_values = self.data.shape[0] * self.data.shape[1]
|
n_values = self.data.shape[0] * self.data.shape[1]
|
||||||
@@ -201,10 +205,10 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
self.parameters.update({"diffexp_may_be_slow": True})
|
self.parameters.update({"diffexp_may_be_slow": True})
|
||||||
|
|
||||||
def _is_valid_layout(self, arr):
|
def _is_valid_layout(self, arr):
|
||||||
""" return True if this layout data is a valid array for front-end presentation:
|
"""return True if this layout data is a valid array for front-end presentation:
|
||||||
* ndarray, dtype float/int/uint
|
* ndarray, dtype float/int/uint
|
||||||
* with shape (n_obs, >= 2)
|
* with shape (n_obs, >= 2)
|
||||||
* with all values finite or NaN (no +Inf or -Inf)
|
* with all values finite or NaN (no +Inf or -Inf)
|
||||||
"""
|
"""
|
||||||
is_valid = type(arr) == np.ndarray and arr.dtype.kind in "fiu"
|
is_valid = type(arr) == np.ndarray and arr.dtype.kind in "fiu"
|
||||||
is_valid = is_valid and arr.shape[0] == self.data.n_obs and arr.shape[1] >= 2
|
is_valid = is_valid and arr.shape[0] == self.data.n_obs and arr.shape[1] >= 2
|
||||||
@@ -327,8 +331,8 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
X = self.data.X[obs_mask, var_mask]
|
X = self.data.X[obs_mask, var_mask]
|
||||||
return X
|
return X
|
||||||
|
|
||||||
def get_X_approx_distribution(self) -> XApproxDistribution:
|
def get_X_approximate_distribution(self) -> XApproximateDistribution:
|
||||||
return self.X_approx_distribution
|
return self.X_approximate_distribution
|
||||||
|
|
||||||
def get_shape(self):
|
def get_shape(self):
|
||||||
return self.data.shape
|
return self.data.shape
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from scipy import sparse
|
|||||||
from server_timing import Timing as ServerTiming
|
from server_timing import Timing as ServerTiming
|
||||||
|
|
||||||
from backend.czi_hosted.common.config.app_config import AppConfig
|
from backend.czi_hosted.common.config.app_config import AppConfig
|
||||||
from backend.common.constants import Axis, XApproxDistribution
|
from backend.common.constants import Axis, XApproximateDistribution
|
||||||
from backend.common.errors import (
|
from backend.common.errors import (
|
||||||
FilterError,
|
FilterError,
|
||||||
JSONEncodingValueError,
|
JSONEncodingValueError,
|
||||||
@@ -84,7 +84,7 @@ class DataAdaptor(metaclass=ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_X_approx_distribution(self) -> XApproxDistribution:
|
def get_X_approximate_distribution(self) -> XApproximateDistribution:
|
||||||
"""return the approximate distribution of the X matrix."""
|
"""return the approximate distribution of the X matrix."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import pandas as pd
|
|||||||
import tiledb
|
import tiledb
|
||||||
from server_timing import Timing as ServerTiming
|
from server_timing import Timing as ServerTiming
|
||||||
|
|
||||||
from backend.common.constants import Axis, XApproxDistribution
|
from backend.common.constants import Axis, XApproximateDistribution
|
||||||
from backend.common.errors import DatasetAccessError, ConfigurationError
|
from backend.common.errors import DatasetAccessError, ConfigurationError
|
||||||
from backend.czi_hosted.common.immutable_kvcache import ImmutableKVCache
|
from backend.czi_hosted.common.immutable_kvcache import ImmutableKVCache
|
||||||
from backend.common.utils.type_conversion_utils import get_schema_type_hint_from_dtype
|
from backend.common.utils.type_conversion_utils import get_schema_type_hint_from_dtype
|
||||||
@@ -37,7 +37,7 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
self.lsuri_results = ImmutableKVCache(lambda key: self._lsuri(uri=key, tiledb_ctx=self.tiledb_ctx))
|
self.lsuri_results = ImmutableKVCache(lambda key: self._lsuri(uri=key, tiledb_ctx=self.tiledb_ctx))
|
||||||
self.arrays = ImmutableKVCache(lambda key: self._open_array(uri=key, tiledb_ctx=self.tiledb_ctx))
|
self.arrays = ImmutableKVCache(lambda key: self._open_array(uri=key, tiledb_ctx=self.tiledb_ctx))
|
||||||
self.schema = None
|
self.schema = None
|
||||||
self.X_approx_distribution = None
|
self.X_approximate_distribution = None
|
||||||
|
|
||||||
self._validate_and_initialize()
|
self._validate_and_initialize()
|
||||||
|
|
||||||
@@ -176,9 +176,9 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
if cxg_version not in ["0.0", "0.1", "0.2.0"]:
|
if cxg_version not in ["0.0", "0.1", "0.2.0"]:
|
||||||
raise DatasetAccessError(f"cxg matrix is not valid: {self.url}")
|
raise DatasetAccessError(f"cxg matrix is not valid: {self.url}")
|
||||||
|
|
||||||
if self.dataset_config.X_approx_distribution == "auto":
|
if self.dataset_config.X_approximate_distribution == "auto":
|
||||||
raise ConfigurationError("X-approx-distribution 'auto' mode unsupported.")
|
raise ConfigurationError("X-approximate-distribution 'auto' mode unsupported.")
|
||||||
self.X_approx_distribution = self.dataset_config.X_approx_distribution
|
self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.about = about
|
self.about = about
|
||||||
@@ -210,7 +210,8 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
if lfc_cutoff is None:
|
if lfc_cutoff is None:
|
||||||
lfc_cutoff = self.dataset_config.diffexp__lfc_cutoff
|
lfc_cutoff = self.dataset_config.diffexp__lfc_cutoff
|
||||||
return diffexp_cxg.diffexp_ttest(
|
return diffexp_cxg.diffexp_ttest(
|
||||||
adaptor=self, maskA=maskA, maskB=maskB, top_n=top_n, diffexp_lfc_cutoff=lfc_cutoff)
|
adaptor=self, maskA=maskA, maskB=maskB, top_n=top_n, diffexp_lfc_cutoff=lfc_cutoff
|
||||||
|
)
|
||||||
|
|
||||||
def get_colors(self):
|
def get_colors(self):
|
||||||
if self.cxg_version == "0.0":
|
if self.cxg_version == "0.0":
|
||||||
@@ -286,8 +287,8 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
data = X.multi_index[obs_items, var_items][""]
|
data = X.multi_index[obs_items, var_items][""]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_X_approx_distribution(self) -> XApproxDistribution:
|
def get_X_approximate_distribution(self) -> XApproximateDistribution:
|
||||||
return self.X_approx_distribution
|
return self.X_approximate_distribution
|
||||||
|
|
||||||
def get_shape(self):
|
def get_shape(self):
|
||||||
X = self.open_array("X")
|
X = self.open_array("X")
|
||||||
@@ -357,7 +358,7 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
shape = self.get_shape()
|
shape = self.get_shape()
|
||||||
dtype = self.get_X_array_dtype()
|
dtype = self.get_X_array_dtype()
|
||||||
|
|
||||||
dataframe = {"nObs": shape[0], "nVar": shape[1], "type": dtype.name}
|
dataframe = {"nObs": shape[0], "nVar": shape[1], **get_schema_type_hint_from_dtype(dtype)}
|
||||||
|
|
||||||
annotations = {}
|
annotations = {}
|
||||||
for ax in ("obs", "var"):
|
for ax in ("obs", "var"):
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ dataset:
|
|||||||
lfc_cutoff: 0.01
|
lfc_cutoff: 0.01
|
||||||
top_n: 10
|
top_n: 10
|
||||||
|
|
||||||
X_approx_distribution: normal # currently fixed config
|
X_approximate_distribution: normal # currently fixed config
|
||||||
|
|
||||||
external:
|
external:
|
||||||
# You can retrieve configuration parameters from this config file, the environment,
|
# You can retrieve configuration parameters from this config file, the environment,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
anndata>=0.7.6 # we use to_memory(), added in 0.7.6
|
anndata>=0.7.6 # we need to_memory(), added in 0.7.6
|
||||||
boto3>=1.12.18
|
boto3>=1.12.18
|
||||||
click>=7.1.2
|
click>=7.1.2
|
||||||
Flask>=1.0.2,<2.0.0 # Flask 2.0 is not compatible with the latest version of Flask-RESTful (0.3.8)
|
Flask>=1.0.2,<2.0.0 # Flask 2.0 is not compatible with the latest version of Flask-RESTful (0.3.8)
|
||||||
Flask-Compress>=1.4.0
|
Flask-Compress>=1.4.0
|
||||||
Flask-Cors>=3.0.6
|
Flask-Cors>=3.0.9 # CVE-2020-25032
|
||||||
Flask-RESTful>=0.3.6
|
Flask-RESTful>=0.3.6
|
||||||
flask-server-timing>=0.1.2
|
flask-server-timing>=0.1.2
|
||||||
flask-talisman>=0.7.0
|
flask-talisman>=0.7.0
|
||||||
@@ -12,12 +12,12 @@ flatten-dict>=0.2.0
|
|||||||
fsspec>=0.4.4,<0.8.0
|
fsspec>=0.4.4,<0.8.0
|
||||||
gunicorn>=20.0.4
|
gunicorn>=20.0.4
|
||||||
h5py>=3.0.0
|
h5py>=3.0.0
|
||||||
numba>=0.49.1,<0.53.0
|
numba>=0.51.2
|
||||||
numpy>=1.15.0
|
numpy>=1.17.5
|
||||||
packaging>=20.0
|
packaging>=20.0
|
||||||
pandas>=1.0,!=1.1 # pandas 1.1 breaks tests, https://github.com/pandas-dev/pandas/issues/35446
|
pandas>=1.0,!=1.1 # pandas 1.1 breaks tests, https://github.com/pandas-dev/pandas/issues/35446
|
||||||
PyYAML>=5.3
|
PyYAML>=5.4 # CVE-2020-14343
|
||||||
scipy>=1.0
|
scipy>=1.4
|
||||||
requests>=2.22.0
|
requests>=2.22.0
|
||||||
tiledb>=0.5.9,>=0.6.2,!=0.7.2, !=0.8.6
|
tiledb>=0.5.9,>=0.6.2,!=0.7.2, !=0.8.6
|
||||||
s3fs==0.4.2
|
s3fs==0.4.2
|
||||||
|
|||||||
@@ -151,8 +151,8 @@ def dataset_args(func):
|
|||||||
help="URL providing more information about the dataset (hint: must be a fully specified absolute URL).",
|
help="URL providing more information about the dataset (hint: must be a fully specified absolute URL).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--X-approx-distribution",
|
"--X-approximate-distribution",
|
||||||
default=DEFAULT_CONFIG.dataset_config.X_approx_distribution,
|
default=DEFAULT_CONFIG.dataset_config.X_approximate_distribution,
|
||||||
show_default=True,
|
show_default=True,
|
||||||
type=click.Choice(["auto", "normal", "count"], case_sensitive=False),
|
type=click.Choice(["auto", "normal", "count"], case_sensitive=False),
|
||||||
help="Specify the approximate distribution of X matrix values. 'auto' will use a heuristic "
|
help="Specify the approximate distribution of X matrix values. 'auto' will use a heuristic "
|
||||||
@@ -326,7 +326,7 @@ def launch(
|
|||||||
disable_diffexp,
|
disable_diffexp,
|
||||||
config_file,
|
config_file,
|
||||||
dump_default_config,
|
dump_default_config,
|
||||||
x_approx_distribution,
|
x_approximate_distribution,
|
||||||
):
|
):
|
||||||
"""Launch the cellxgene data viewer.
|
"""Launch the cellxgene data viewer.
|
||||||
This web app lets you explore single-cell expression data.
|
This web app lets you explore single-cell expression data.
|
||||||
@@ -385,7 +385,7 @@ def launch(
|
|||||||
embeddings__names=embedding,
|
embeddings__names=embedding,
|
||||||
diffexp__enable=not disable_diffexp,
|
diffexp__enable=not disable_diffexp,
|
||||||
diffexp__lfc_cutoff=diffexp_lfc_cutoff,
|
diffexp__lfc_cutoff=diffexp_lfc_cutoff,
|
||||||
X_approx_distribution=x_approx_distribution,
|
X_approximate_distribution=x_approximate_distribution,
|
||||||
)
|
)
|
||||||
|
|
||||||
diff = cli_config.server_config.changes_from_default()
|
diff = cli_config.server_config.changes_from_default()
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class DatasetConfig(BaseConfig):
|
|||||||
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
|
self.diffexp__lfc_cutoff = default_config["diffexp"]["lfc_cutoff"]
|
||||||
self.diffexp__top_n = default_config["diffexp"]["top_n"]
|
self.diffexp__top_n = default_config["diffexp"]["top_n"]
|
||||||
|
|
||||||
self.X_approx_distribution = default_config["X_approx_distribution"]
|
self.X_approximate_distribution = default_config["X_approximate_distribution"]
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise ConfigurationError(f"Unexpected config: {str(e)}")
|
raise ConfigurationError(f"Unexpected config: {str(e)}")
|
||||||
@@ -52,7 +52,7 @@ class DatasetConfig(BaseConfig):
|
|||||||
self.handle_user_annotations(context)
|
self.handle_user_annotations(context)
|
||||||
self.handle_embeddings()
|
self.handle_embeddings()
|
||||||
self.handle_diffexp(context)
|
self.handle_diffexp(context)
|
||||||
self.handle_X_approx_distribution()
|
self.handle_X_approximate_distribution()
|
||||||
|
|
||||||
def get_data_adaptor(self):
|
def get_data_adaptor(self):
|
||||||
server_config = self.app_config.server_config
|
server_config = self.app_config.server_config
|
||||||
@@ -186,9 +186,9 @@ class DatasetConfig(BaseConfig):
|
|||||||
"CAUTION: due to the size of your dataset, " "running differential expression may take longer or fail."
|
"CAUTION: due to the size of your dataset, " "running differential expression may take longer or fail."
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_X_approx_distribution(self):
|
def handle_X_approximate_distribution(self):
|
||||||
self.validate_correct_type_of_configuration_attribute("X_approx_distribution", str)
|
self.validate_correct_type_of_configuration_attribute("X_approximate_distribution", str)
|
||||||
if self.X_approx_distribution not in ["auto", "normal", "count"]:
|
if self.X_approximate_distribution not in ["auto", "normal", "count"]:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
"X_approx_distribution has unknown value -- must be 'auto', 'normal' or 'count'."
|
"X_approximate_distribution has unknown value -- must be 'auto', 'normal' or 'count'."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from scipy import sparse
|
|||||||
import backend.common.compute.diffexp_generic as diffexp_generic
|
import backend.common.compute.diffexp_generic as diffexp_generic
|
||||||
import backend.common.compute.estimate_distribution as estimate_distribution
|
import backend.common.compute.estimate_distribution as estimate_distribution
|
||||||
from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors
|
from backend.common.colors import convert_anndata_category_colors_to_cxg_category_colors
|
||||||
from backend.common.constants import Axis, MAX_LAYOUTS, XApproxDistribution
|
from backend.common.constants import Axis, MAX_LAYOUTS, XApproximateDistribution
|
||||||
from backend.server.common.corpora import corpora_get_props_from_anndata
|
from backend.server.common.corpora import corpora_get_props_from_anndata
|
||||||
from backend.common.errors import PrepareError, DatasetAccessError
|
from backend.common.errors import PrepareError, DatasetAccessError
|
||||||
from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array
|
from backend.common.utils.type_conversion_utils import get_schema_type_hint_of_array
|
||||||
@@ -29,7 +29,7 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
def __init__(self, data_locator, app_config=None, dataset_config=None):
|
def __init__(self, data_locator, app_config=None, dataset_config=None):
|
||||||
super().__init__(data_locator, app_config, dataset_config)
|
super().__init__(data_locator, app_config, dataset_config)
|
||||||
self.data = None
|
self.data = None
|
||||||
self.X_approx_distribution = None
|
self.X_approximate_distribution = None
|
||||||
self._load_data(data_locator)
|
self._load_data(data_locator)
|
||||||
self._validate_and_initialize()
|
self._validate_and_initialize()
|
||||||
|
|
||||||
@@ -125,7 +125,11 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
|
|
||||||
def _create_schema(self):
|
def _create_schema(self):
|
||||||
self.schema = {
|
self.schema = {
|
||||||
"dataframe": {"nObs": self.cell_count, "nVar": self.gene_count, "type": str(self.data.X.dtype)},
|
"dataframe": {
|
||||||
|
"nObs": self.cell_count,
|
||||||
|
"nVar": self.gene_count,
|
||||||
|
**get_schema_type_hint_of_array(self.data.X),
|
||||||
|
},
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"obs": {"index": self.parameters.get("obs_names"), "columns": []},
|
"obs": {"index": self.parameters.get("obs_names"), "columns": []},
|
||||||
"var": {"index": self.parameters.get("var_names"), "columns": []},
|
"var": {"index": self.parameters.get("var_names"), "columns": []},
|
||||||
@@ -192,12 +196,12 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
self.gene_count = self.data.shape[1]
|
self.gene_count = self.data.shape[1]
|
||||||
self._create_schema()
|
self._create_schema()
|
||||||
|
|
||||||
if self.dataset_config.X_approx_distribution == "auto":
|
if self.dataset_config.X_approximate_distribution == "auto":
|
||||||
"""Lazy evaluate the heuristic if we are backed."""
|
"""Lazy evaluate the heuristic if we are backed."""
|
||||||
if not self.data.isbacked:
|
if not self.data.isbacked:
|
||||||
self.X_approx_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X)
|
self.X_approximate_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X)
|
||||||
else:
|
else:
|
||||||
self.X_approx_distribution = self.dataset_config.X_approx_distribution
|
self.X_approximate_distribution = self.dataset_config.X_approximate_distribution
|
||||||
|
|
||||||
# heuristic
|
# heuristic
|
||||||
n_values = self.data.shape[0] * self.data.shape[1]
|
n_values = self.data.shape[0] * self.data.shape[1]
|
||||||
@@ -331,15 +335,15 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
X = self.data.X[obs_mask, var_mask]
|
X = self.data.X[obs_mask, var_mask]
|
||||||
return X
|
return X
|
||||||
|
|
||||||
def get_X_approx_distribution(self) -> XApproxDistribution:
|
def get_X_approximate_distribution(self) -> XApproximateDistribution:
|
||||||
"""return the approximate distribution of the X matrix."""
|
"""return the approximate distribution of the X matrix."""
|
||||||
if self.X_approx_distribution is None:
|
if self.X_approximate_distribution is None:
|
||||||
"""Not yet evaluated."""
|
"""Not yet evaluated."""
|
||||||
assert(self.dataset_config.X_approx_distribution == "auto")
|
assert self.dataset_config.X_approximate_distribution == "auto"
|
||||||
self.data = self.data.to_memory() # loads data
|
self.data = self.data.to_memory() # loads data
|
||||||
self.X_approx_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X)
|
self.X_approximate_distribution = estimate_distribution.estimate_approximate_distribution(self.data.X)
|
||||||
|
|
||||||
return self.X_approx_distribution
|
return self.X_approximate_distribution
|
||||||
|
|
||||||
def get_shape(self):
|
def get_shape(self):
|
||||||
return self.data.shape
|
return self.data.shape
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from scipy import sparse
|
|||||||
from server_timing import Timing as ServerTiming
|
from server_timing import Timing as ServerTiming
|
||||||
|
|
||||||
from backend.server.common.config.app_config import AppConfig
|
from backend.server.common.config.app_config import AppConfig
|
||||||
from backend.common.constants import Axis, XApproxDistribution
|
from backend.common.constants import Axis, XApproximateDistribution
|
||||||
from backend.common.errors import FilterError, JSONEncodingValueError, ExceedsLimitError, UnsupportedSummaryMethod
|
from backend.common.errors import FilterError, JSONEncodingValueError, ExceedsLimitError, UnsupportedSummaryMethod
|
||||||
from backend.common.utils.utils import jsonify_numpy
|
from backend.common.utils.utils import jsonify_numpy
|
||||||
from backend.common.fbs.matrix import encode_matrix_fbs
|
from backend.common.fbs.matrix import encode_matrix_fbs
|
||||||
@@ -72,9 +72,9 @@ class DataAdaptor(metaclass=ABCMeta):
|
|||||||
the return type is either ndarray or scipy.sparse.spmatrix."""
|
the return type is either ndarray or scipy.sparse.spmatrix."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_X_approx_distribution(self) -> XApproxDistribution:
|
def get_X_approximate_distribution(self) -> XApproximateDistribution:
|
||||||
"""return the approximate distribution of the X matrix."""
|
"""return the approximate distribution of the X matrix."""
|
||||||
return XApproxDistribution.NORMAL
|
return XApproximateDistribution.NORMAL
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_shape(self):
|
def get_shape(self):
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ dataset:
|
|||||||
lfc_cutoff: 0.01
|
lfc_cutoff: 0.01
|
||||||
top_n: 10
|
top_n: 10
|
||||||
|
|
||||||
X_approx_distribution: auto
|
X_approximate_distribution: auto
|
||||||
|
|
||||||
external:
|
external:
|
||||||
# You can retrieve configuration parameters from this config file, the environment,
|
# You can retrieve configuration parameters from this config file, the environment,
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ dataset:
|
|||||||
lfc_cutoff: {lfc_cutoff}
|
lfc_cutoff: {lfc_cutoff}
|
||||||
top_n: {top_n}
|
top_n: {top_n}
|
||||||
|
|
||||||
X_approx_distribution: {X_approx_distribution}
|
X_approximate_distribution: {X_approximate_distribution}
|
||||||
"""
|
"""
|
||||||
|
|||||||
+1
-1
@@ -28,5 +28,5 @@ dataset:
|
|||||||
lfc_cutoff: {lfc_cutoff}
|
lfc_cutoff: {lfc_cutoff}
|
||||||
top_n: {top_n}
|
top_n: {top_n}
|
||||||
|
|
||||||
X_approx_distribution: {X_approx_distribution}
|
X_approximate_distribution: {X_approximate_distribution}
|
||||||
"""
|
"""
|
||||||
|
|||||||
+1
-1
@@ -16,6 +16,6 @@ summary test,,F5,
|
|||||||
summary test,,PIGU,
|
summary test,,PIGU,
|
||||||
geneset_to_delete,,,
|
geneset_to_delete,,,
|
||||||
geneset_to_edit,,,
|
geneset_to_edit,,,
|
||||||
fill_this_geneset,,RER1,
|
fill_this_geneset,,,
|
||||||
empty_this_geneset,,SIK1,
|
empty_this_geneset,,SIK1,
|
||||||
brush_this_gene,,SIK1,
|
brush_this_gene,,SIK1,
|
||||||
|
Vendored
BIN
Binary file not shown.
@@ -2,16 +2,20 @@ import unittest
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy import sparse
|
from scipy import sparse
|
||||||
|
from parameterized import parameterized_class
|
||||||
|
import json
|
||||||
|
|
||||||
import backend.test.decode_fbs as decode_fbs
|
from backend.test import decode_fbs
|
||||||
from backend.common.fbs.matrix import encode_matrix_fbs, decode_matrix_fbs
|
from backend.common.fbs.matrix import encode_matrix_fbs, decode_matrix_fbs
|
||||||
|
from backend.common.utils.type_conversion_utils import get_dtypes_and_schemas_of_dataframe
|
||||||
|
import backend.common.fbs as fbs
|
||||||
|
|
||||||
|
|
||||||
class FbsTests(unittest.TestCase):
|
class FbsTests(unittest.TestCase):
|
||||||
"""Test Case for Matrix FBS data encode/decode """
|
"""Test Case for Matrix FBS data encode/decode"""
|
||||||
|
|
||||||
def test_encode_boundary(self):
|
def test_encode_boundary(self):
|
||||||
""" test various boundary checks """
|
"""test various boundary checks"""
|
||||||
|
|
||||||
# row indexing is unsupported
|
# row indexing is unsupported
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
@@ -46,7 +50,7 @@ class FbsTests(unittest.TestCase):
|
|||||||
"d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"),
|
"d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.uint32), (list, None))
|
expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.int32), (list, None))
|
||||||
fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns)
|
fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns)
|
||||||
self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"])
|
self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"])
|
||||||
|
|
||||||
@@ -80,3 +84,133 @@ class FbsTests(unittest.TestCase):
|
|||||||
self.assertTrue(np.all(dfSrc[c] == dfDst[c]))
|
self.assertTrue(np.all(dfSrc[c] == dfDst[c]))
|
||||||
else:
|
else:
|
||||||
self.assertEqual(dfSrc[c], dfDst[c])
|
self.assertEqual(dfSrc[c], dfDst[c])
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test type consistency between FBS encoding and the underlying schema hint.
|
||||||
|
|
||||||
|
Basic assertion: the FBS type returned by encode_matrix_fbs() will be consistent
|
||||||
|
with the schema hint returned by type_conversion_utils (which is in turn used
|
||||||
|
to create the client schema).
|
||||||
|
|
||||||
|
The following test cases are all dicts which contain the following keys:
|
||||||
|
- dataframe - the dataframe used as input for encode_matrix_fbs
|
||||||
|
- expected_fbs_types - upon success, dict of FBS column types expected (eg, Float32Array)
|
||||||
|
- expected_schema_hints - upon success, dict of schema hint
|
||||||
|
All are keyed by column name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# simple tests that we convert all ints to int32
|
||||||
|
int_dtypes = [np.dtype(d) for d in [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64]]
|
||||||
|
int_test_cases = [
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame({dtype.name: np.zeros((10,), dtype=dtype) for dtype in int_dtypes}),
|
||||||
|
"expected_fbs_types": dict(
|
||||||
|
[(dtype.name, fbs.NetEncoding.TypedArray.TypedArray.Int32Array) for dtype in int_dtypes]
|
||||||
|
),
|
||||||
|
"expected_schema_hints": dict([(dtype.name, {"type": "int32"}) for dtype in int_dtypes]),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# simple tests that we convert all floats to float32
|
||||||
|
float_dtypes = [np.dtype(d) for d in [np.float16, np.float32, np.float64]]
|
||||||
|
float_test_cases = [
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame({dtype.name: np.zeros((10,), dtype=dtype) for dtype in float_dtypes}),
|
||||||
|
"expected_fbs_types": dict(
|
||||||
|
[(dtype.name, fbs.NetEncoding.TypedArray.TypedArray.Float32Array) for dtype in float_dtypes]
|
||||||
|
),
|
||||||
|
"expected_schema_hints": dict([(dtype.name, {"type": "float32"}) for dtype in float_dtypes]),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# boolean - should be encoded as an uint32
|
||||||
|
bool_dtypes = [np.dtype(d) for d in [np.bool_, bool]]
|
||||||
|
bool_test_cases = [
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame({dtype.name: np.ones((10,), dtype=dtype) for dtype in bool_dtypes}),
|
||||||
|
"expected_fbs_types": dict(
|
||||||
|
[(dtype.name, fbs.NetEncoding.TypedArray.TypedArray.Uint32Array) for dtype in bool_dtypes]
|
||||||
|
),
|
||||||
|
"expected_schema_hints": dict([(dtype.name, {"type": "boolean"}) for dtype in bool_dtypes]),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cat_test_cases = [
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame({"a": pd.Series(["a", "b", "c", "a", "b", "c"], dtype="category")}),
|
||||||
|
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.JSONEncodedArray},
|
||||||
|
"expected_schema_hints": {"a": {"type": "categorical", "categories": ["a", "b", "c"]}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame(
|
||||||
|
{"a": pd.Series(["a", "b", "c", "a", "b", "c"], dtype="category").cat.remove_categories("b")}
|
||||||
|
),
|
||||||
|
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.JSONEncodedArray},
|
||||||
|
"expected_schema_hints": {"a": {"type": "categorical", "categories": ["a", "c"]}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame({"a": pd.Series(np.arange(0, 10, dtype=np.int64), dtype="category")}),
|
||||||
|
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Int32Array},
|
||||||
|
"expected_schema_hints": {"a": {"type": "categorical"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame(
|
||||||
|
{"a": pd.Series(np.arange(0, 10, dtype=np.int64), dtype="category").cat.remove_categories(2)}
|
||||||
|
),
|
||||||
|
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Float32Array},
|
||||||
|
"expected_schema_hints": {"a": {"type": "categorical"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame({"a": pd.Series(np.arange(0, 10, dtype=np.float64), dtype="category")}),
|
||||||
|
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Float32Array},
|
||||||
|
"expected_schema_hints": {"a": {"type": "categorical"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dataframe": pd.DataFrame(
|
||||||
|
{"a": pd.Series(np.arange(0, 10, dtype=np.float64), dtype="category").cat.remove_categories(2)}
|
||||||
|
),
|
||||||
|
"expected_fbs_types": {"a": fbs.NetEncoding.TypedArray.TypedArray.Float32Array},
|
||||||
|
"expected_schema_hints": {"a": {"type": "categorical"}},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
*int_test_cases,
|
||||||
|
*float_test_cases,
|
||||||
|
*bool_test_cases,
|
||||||
|
*cat_test_cases,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@parameterized_class(test_cases)
|
||||||
|
class TestTypeConversionConsistency(unittest.TestCase):
|
||||||
|
def test_type_conversion_consistency(self):
|
||||||
|
self.assertEqual(self.dataframe.shape[1], len(self.expected_fbs_types))
|
||||||
|
self.assertEqual(self.dataframe.shape[1], len(self.expected_schema_hints))
|
||||||
|
|
||||||
|
buf = encode_matrix_fbs(matrix=self.dataframe, col_idx=self.dataframe.columns)
|
||||||
|
encoding_dtypes, schema_hints = get_dtypes_and_schemas_of_dataframe(self.dataframe)
|
||||||
|
|
||||||
|
# check schema hints
|
||||||
|
# print(schema_hints)
|
||||||
|
# print(self.expected_schema_hints)
|
||||||
|
self.assertEqual(schema_hints, self.expected_schema_hints)
|
||||||
|
|
||||||
|
# inspect the FBS types
|
||||||
|
matrix = fbs.NetEncoding.Matrix.Matrix.GetRootAsMatrix(buf, 0)
|
||||||
|
columns_length = matrix.ColumnsLength()
|
||||||
|
self.assertEqual(columns_length, self.dataframe.shape[1])
|
||||||
|
|
||||||
|
self.assertEqual(matrix.ColIndexType(), fbs.NetEncoding.TypedArray.TypedArray.JSONEncodedArray)
|
||||||
|
col_labels_arr = fbs.NetEncoding.JSONEncodedArray.JSONEncodedArray()
|
||||||
|
col_labels_arr.Init(matrix.ColIndex().Bytes, matrix.ColIndex().Pos)
|
||||||
|
col_index_labels = json.loads(col_labels_arr.DataAsNumpy().tobytes().decode("utf-8"))
|
||||||
|
self.assertEqual(len(col_index_labels), self.dataframe.shape[1])
|
||||||
|
|
||||||
|
for col_idx in range(0, columns_length):
|
||||||
|
col_label = col_index_labels[col_idx]
|
||||||
|
col = matrix.Columns(col_idx)
|
||||||
|
col_type = col.UType()
|
||||||
|
self.assertEqual(self.expected_fbs_types[col_label], col_type)
|
||||||
|
|||||||
@@ -1,179 +1,22 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from time import time
|
import logging
|
||||||
from unittest.mock import patch
|
from parameterized import parameterized_class
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
from pandas import Series, DataFrame
|
from pandas import Series, DataFrame
|
||||||
|
from scipy import sparse
|
||||||
|
|
||||||
from backend.common.utils.type_conversion_utils import (
|
from backend.common.utils.type_conversion_utils import (
|
||||||
can_cast_to_float32,
|
get_encoding_dtype_of_array,
|
||||||
can_cast_to_int32,
|
|
||||||
get_dtype_of_array,
|
|
||||||
get_schema_type_hint_of_array,
|
get_schema_type_hint_of_array,
|
||||||
get_dtypes_and_schemas_of_dataframe,
|
get_dtypes_and_schemas_of_dataframe,
|
||||||
convert_pandas_series_to_numpy,
|
get_dtype_and_schema_of_array,
|
||||||
|
get_schema_type_hint_from_dtype,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestTypeConversionUtils(unittest.TestCase):
|
class TestTypeConversionUtils(unittest.TestCase):
|
||||||
def test__can_cast_to_float32__string_is_false(self):
|
|
||||||
array_to_convert = Series(data=["1", "2", "3"], dtype=str)
|
|
||||||
|
|
||||||
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertFalse(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_float32__float64_is_true_warning_outputted(self):
|
|
||||||
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.float64))
|
|
||||||
|
|
||||||
with self.assertLogs(level="WARN") as logger:
|
|
||||||
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
|
|
||||||
self.assertIn("may lose precision", logger.output[0])
|
|
||||||
|
|
||||||
self.assertTrue(can_cast)
|
|
||||||
|
|
||||||
@patch("logging.warning")
|
|
||||||
def test__can_cast_to_float32__float32_is_false(self, mock_log_warning):
|
|
||||||
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.float32))
|
|
||||||
|
|
||||||
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertTrue(can_cast)
|
|
||||||
assert not mock_log_warning.called
|
|
||||||
|
|
||||||
def test__can_cast_to_float32__categorical_float64_is_false(self):
|
|
||||||
array_to_convert = Series(data=[1.1, 2.2, 3.3], dtype="category")
|
|
||||||
|
|
||||||
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertFalse(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_float32__categorical_int64_with_nans_is_true(self):
|
|
||||||
array_to_convert = Series(data=[1, 2, np.NaN], dtype="category")
|
|
||||||
|
|
||||||
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertTrue(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_float_32__float_32_with_nans_is_true(self):
|
|
||||||
array_to_convert = Series(data=[1, 2, np.NaN], dtype=np.dtype(np.float32))
|
|
||||||
|
|
||||||
can_cast = can_cast_to_float32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertTrue(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_int32__string_is_false(self):
|
|
||||||
array_to_convert = Series(data=["1", "2", "3"], dtype=str)
|
|
||||||
|
|
||||||
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertFalse(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_int32__int64_is_true(self):
|
|
||||||
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.int64))
|
|
||||||
|
|
||||||
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertTrue(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_int32__int16_is_true(self):
|
|
||||||
array_to_convert = Series(data=[1, 2, 3], dtype=np.dtype(np.int16))
|
|
||||||
|
|
||||||
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertTrue(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_int32__int64_with_large_value_is_false(self):
|
|
||||||
array_to_convert = Series(data=[3000000000, 2, 3], dtype=np.dtype(np.int64))
|
|
||||||
|
|
||||||
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertFalse(can_cast)
|
|
||||||
|
|
||||||
def test__can_cast_to_int32__int64_with_nans_is_false(self):
|
|
||||||
array_to_convert = Series(data=[np.NaN, "2", "3"], dtype="category")
|
|
||||||
|
|
||||||
can_cast = can_cast_to_int32(array_to_convert.dtype, array_to_convert)
|
|
||||||
|
|
||||||
self.assertFalse(can_cast)
|
|
||||||
|
|
||||||
def test__get_dtype_of_array__supported_dtypes_return_as_expected(self):
|
|
||||||
types = [np.float32, np.int32, np.bool_, str]
|
|
||||||
expected_dtypes = [np.float32, np.int32, np.uint8, str]
|
|
||||||
|
|
||||||
for test_type_index in range(len(types)):
|
|
||||||
with self.subTest(
|
|
||||||
f"Testing get_dtype_of_array with type {types[test_type_index].__name__}", i=test_type_index
|
|
||||||
):
|
|
||||||
array = Series(data=[], dtype=types[test_type_index])
|
|
||||||
self.assertEqual(get_dtype_of_array(array), expected_dtypes[test_type_index])
|
|
||||||
|
|
||||||
def test__get_dtype_of_array__categories_return_as_expected(self):
|
|
||||||
array = Series(data=["a", "b", "c"], dtype="category")
|
|
||||||
expected_dtype = str
|
|
||||||
|
|
||||||
actual_dtype = get_dtype_of_array(array)
|
|
||||||
|
|
||||||
self.assertEqual(expected_dtype, actual_dtype)
|
|
||||||
|
|
||||||
def test__get_dtype_of_array__unordered_integer_categories_return_as_expected(self):
|
|
||||||
array = Series(data=[2, 3, 1, 3, 1, 2], dtype="category")
|
|
||||||
expected_dtype = np.int32
|
|
||||||
|
|
||||||
actual_dtype = get_dtype_of_array(array)
|
|
||||||
|
|
||||||
self.assertEqual(expected_dtype, actual_dtype)
|
|
||||||
|
|
||||||
def test__get_dtype_of_array__castable_dtypes_return_as_expected(self):
|
|
||||||
types = [np.float64, np.int64]
|
|
||||||
expected_dtypes = [np.float32, np.int32]
|
|
||||||
|
|
||||||
for test_type_index in range(len(types)):
|
|
||||||
with self.subTest(
|
|
||||||
f"Testing get_dtype_of_array with castable type {types[test_type_index].__name__}", i=test_type_index
|
|
||||||
):
|
|
||||||
array = Series(data=[], dtype=types[test_type_index])
|
|
||||||
self.assertEqual(get_dtype_of_array(array), expected_dtypes[test_type_index])
|
|
||||||
|
|
||||||
def test__get_dtype_of_array__unsupported_type_raises_exception(self):
|
|
||||||
unsupported_array = Series(list([time() for _ in range(2)]), dtype="datetime64[ns]")
|
|
||||||
|
|
||||||
with self.assertRaises(TypeError) as exception_context:
|
|
||||||
get_dtype_of_array(unsupported_array)
|
|
||||||
|
|
||||||
self.assertIn("unsupported", str(exception_context.exception))
|
|
||||||
|
|
||||||
def test__get_schema_type_hint_of_array__supported_dtypes_return_as_expected(self):
|
|
||||||
types = [np.float32, np.int32, np.bool_, str]
|
|
||||||
expected_schema_hints = [{"type": "float32"}, {"type": "int32"}, {"type": "boolean"}, {"type": "string"}]
|
|
||||||
|
|
||||||
for test_type_index in range(len(types)):
|
|
||||||
with self.subTest(
|
|
||||||
f"Testing get_schema_type_hint_of_array with type {types[test_type_index].__name__}", i=test_type_index
|
|
||||||
):
|
|
||||||
array = Series(data=[], dtype=types[test_type_index])
|
|
||||||
self.assertEqual(get_schema_type_hint_of_array(array), expected_schema_hints[test_type_index])
|
|
||||||
|
|
||||||
def test__get_schema_type_hint_of_array__categories_return_as_expected(self):
|
|
||||||
array = Series(data=["a", "b", "b"], dtype="category")
|
|
||||||
expected_schema_hint = {"type": "categorical", "categories": ["a", "b"]}
|
|
||||||
|
|
||||||
actual_schema_hint = get_schema_type_hint_of_array(array)
|
|
||||||
|
|
||||||
self.assertEqual(expected_schema_hint, actual_schema_hint)
|
|
||||||
|
|
||||||
def test__get_schema_type_hint_of_array__castable_dtypes_return_as_expected(self):
|
|
||||||
types = [np.float64, np.int64]
|
|
||||||
expected_schema_hints = [{"type": "float32"}, {"type": "int32"}]
|
|
||||||
|
|
||||||
for test_type_index in range(len(types)):
|
|
||||||
with self.subTest(
|
|
||||||
f"Testing get_schema_type_hint_of_array with castable type {types[test_type_index].__name__}",
|
|
||||||
i=test_type_index,
|
|
||||||
):
|
|
||||||
array = Series(data=[], dtype=types[test_type_index])
|
|
||||||
self.assertEqual(get_schema_type_hint_of_array(array), expected_schema_hints[test_type_index])
|
|
||||||
|
|
||||||
def test__get_dtypes_and_schemas_of_dataframe__dtype_and_schema_returns_as_expected(self):
|
def test__get_dtypes_and_schemas_of_dataframe__dtype_and_schema_returns_as_expected(self):
|
||||||
float_array = Series(data=[1, 2, 3], dtype=np.dtype(np.float64))
|
float_array = Series(data=[1, 2, 3], dtype=np.dtype(np.float64))
|
||||||
category_array = Series(data=["a", "b", "b"], dtype="category")
|
category_array = Series(data=["a", "b", "b"], dtype="category")
|
||||||
@@ -190,28 +33,292 @@ class TestTypeConversionUtils(unittest.TestCase):
|
|||||||
self.assertEqual(expected_data_types_dict, actual_dataframe_data_types)
|
self.assertEqual(expected_data_types_dict, actual_dataframe_data_types)
|
||||||
self.assertEqual(expected_schema_type_hints_dict, actual_dataframe_schema_type_hints)
|
self.assertEqual(expected_schema_type_hints_dict, actual_dataframe_schema_type_hints)
|
||||||
|
|
||||||
def test__convert_pandas_series_to_numpy__categorical_float64_to_float64_with_nans(self):
|
def test__get_schema_type_hint_from_dtype(self):
|
||||||
expected_float_array = np.array([1.1, 2.2, np.NaN], dtype=np.float64)
|
self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(np.bool_)), {"type": "boolean"})
|
||||||
float_series = Series(data=[1.1, 2.2, np.NaN], dtype="category")
|
|
||||||
|
|
||||||
actual_float_array = convert_pandas_series_to_numpy(float_series, np.float64)
|
for dtype in [np.int8, np.int8, np.int16, np.uint16, np.int32]:
|
||||||
|
self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(dtype)), {"type": "int32"})
|
||||||
|
for dtype in [np.uint32, np.int64, np.uint64]:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
get_schema_type_hint_from_dtype(np.dtype(dtype))
|
||||||
|
|
||||||
np.testing.assert_equal(expected_float_array, actual_float_array)
|
for dtype in [np.float16, np.float32, np.float64]:
|
||||||
|
self.assertEqual(get_schema_type_hint_from_dtype(np.dtype(dtype)), {"type": "float32"})
|
||||||
|
|
||||||
def test__convert_pandas_series_to_numpy__float64_to_float64(self):
|
for dtype in [np.dtype(object), np.dtype(str)]:
|
||||||
expected_float_array = np.array([1.1, 2.2], dtype=np.float64)
|
self.assertEqual(get_schema_type_hint_from_dtype(dtype), {"type": "string"})
|
||||||
float_series = Series(data=[1.1, 2.2], dtype=np.dtype(np.float64))
|
|
||||||
|
|
||||||
actual_float_array = convert_pandas_series_to_numpy(float_series, np.float64)
|
|
||||||
|
|
||||||
np.testing.assert_equal(expected_float_array, actual_float_array)
|
# Credit: https://stackoverflow.com/questions/35871815/python-3-unit-testing-assert-logger-not-called/64774103#64774103
|
||||||
|
class AssertNoLog:
|
||||||
|
def assertNoLogs(self, logger, level):
|
||||||
|
"""functions as a context manager. To be introduced in python 3.10"""
|
||||||
|
|
||||||
def test__convert_pandas_series_to_numpy__int64_to_int32_with_nans_throws_error(self):
|
class AssertNoLogsContext(unittest.TestCase):
|
||||||
int_series = Series(data=[1, 2, np.NaN], dtype="category")
|
def __init__(self, logger, level):
|
||||||
|
self.logger = logger
|
||||||
|
self.level = level
|
||||||
|
self.context = self.assertLogs(logger, level)
|
||||||
|
|
||||||
with self.assertLogs(level="ERROR") as logger:
|
def __enter__(self):
|
||||||
convert_pandas_series_to_numpy(int_series, np.int32)
|
"""enter self.assertLogs as context manager, and log something"""
|
||||||
|
self.initial_logmsg = "sole message"
|
||||||
|
self.cm = self.context.__enter__()
|
||||||
|
self.logger.log(self.level, self.initial_logmsg)
|
||||||
|
return self.cm
|
||||||
|
|
||||||
self.assertIn(
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
"Cannot convert a pandas Series object to an integer dtype if it contains NaNs", logger.output[0]
|
"""cleanup logs, and then check nothing extra was logged"""
|
||||||
)
|
# assertLogs.__exit__ should never fail because of initial msg
|
||||||
|
self.context.__exit__(exc_type, exc_val, exc_tb)
|
||||||
|
if len(self.cm.output) > 1:
|
||||||
|
"""override any exception passed to __exit__"""
|
||||||
|
self.context._raiseFailure(
|
||||||
|
"logs of level {} or higher triggered on {} : {}".format(
|
||||||
|
logging.getLevelName(self.level), self.logger.name, self.cm.output[1:]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return AssertNoLogsContext(logger, level)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
See table of expected cases in type_conversion_utils.py.
|
||||||
|
|
||||||
|
This probes all edge cases. Each case is a dict containing keys:
|
||||||
|
- data - the array to be introspected
|
||||||
|
- throws - if not None, the expected Error (eg, TypeError)
|
||||||
|
- expected_encoding_dtype - upon success
|
||||||
|
- expected_schema_hint - upon success
|
||||||
|
- logs - if not None, specify expected log output
|
||||||
|
"""
|
||||||
|
|
||||||
|
bool_OK_cases = [
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.uint8,
|
||||||
|
"expected_schema_hint": {"type": "boolean"},
|
||||||
|
}
|
||||||
|
for data in [
|
||||||
|
np.array([0, 1, 0, 1], dtype=np.bool_),
|
||||||
|
pd.Series(np.array([0, 1, 0, 1], dtype=np.bool_)),
|
||||||
|
# pd.Index with bools doesn't really make any sense...and becomes dtype=object
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
int_OK_cases = [
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.int32,
|
||||||
|
"expected_schema_hint": {"type": "int32"},
|
||||||
|
}
|
||||||
|
for dtype in [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64]
|
||||||
|
for data in [
|
||||||
|
np.arange(0, 1000, dtype=dtype),
|
||||||
|
pd.Series(np.arange(0, 1000, dtype=dtype)),
|
||||||
|
pd.Index(np.arange(0, 1000, dtype=dtype)),
|
||||||
|
sparse.csr_matrix((10, 100), dtype=dtype),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
float_OK_cases = [
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.float32,
|
||||||
|
"expected_schema_hint": {"type": "float32"},
|
||||||
|
"logs": None if data.dtype != np.float64 else {"level": logging.WARNING, "output": "may lose precision"},
|
||||||
|
}
|
||||||
|
for dtype in [np.float16, np.float32, np.float64]
|
||||||
|
for data in [
|
||||||
|
np.arange(-128, 1000, dtype=dtype),
|
||||||
|
pd.Series(np.arange(-128, 1000, dtype=dtype)),
|
||||||
|
pd.Index(np.arange(-129, 1000, dtype=dtype)),
|
||||||
|
np.array([-np.nan, np.NINF, -1, np.NZERO, 0, np.PZERO, 1, np.PINF, np.nan], dtype=dtype),
|
||||||
|
np.array([np.finfo(dtype).min, 0, np.finfo(dtype).max], dtype=dtype),
|
||||||
|
sparse.csr_matrix((10, 100), dtype=dtype),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
numeric_ERR_cases = [
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"throws": TypeError,
|
||||||
|
}
|
||||||
|
for data in [
|
||||||
|
np.array([np.iinfo(np.int64).min, np.iinfo(np.int64).max], dtype=np.int64),
|
||||||
|
np.array([np.iinfo(np.uint64).min, np.iinfo(np.uint64).max], dtype=np.uint64),
|
||||||
|
np.array([np.iinfo(np.uint32).min, np.iinfo(np.uint32).max], dtype=np.uint32),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
string_OK_cases = [
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.dtype(str),
|
||||||
|
"expected_schema_hint": {"type": "string"},
|
||||||
|
}
|
||||||
|
for data in [
|
||||||
|
np.array(["a", "b", "c"]),
|
||||||
|
np.array(["a", "b", "c"], dtype="object"),
|
||||||
|
pd.Series(["a", "b", "c"]),
|
||||||
|
pd.Index(["a", "b", "c"]),
|
||||||
|
np.array(["a", [], {}, None, True, False, 383.2], dtype="object"),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
category_nonnumeric_OK_cases = [
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.dtype(str),
|
||||||
|
"expected_schema_hint": {"type": "categorical", "categories": data.dtype.categories.to_list()},
|
||||||
|
}
|
||||||
|
for data in [
|
||||||
|
pd.Series(["a", "b", "c"], dtype="category"),
|
||||||
|
pd.Series(["a", "b", "c", 0, 1, 2], dtype="category"),
|
||||||
|
pd.Series(["a", "b", "c"], dtype="category").cat.remove_categories(["b"]),
|
||||||
|
pd.Series(["a", "b", "c", 0, 1, 2], dtype="category").cat.remove_categories(["b", 0]),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
category_numeric_OK_cases = [
|
||||||
|
# numeric, no NA/NaN, int
|
||||||
|
*[
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.int32,
|
||||||
|
"expected_schema_hint": {"type": "categorical"},
|
||||||
|
}
|
||||||
|
for dtype in [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64]
|
||||||
|
for data in [
|
||||||
|
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category"),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
# numeric, no NA/NaN, float
|
||||||
|
*[
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.float32,
|
||||||
|
"expected_schema_hint": {"type": "categorical"},
|
||||||
|
"logs": {"level": logging.WARNING, "output": "may lose precision"},
|
||||||
|
}
|
||||||
|
for dtype in [np.float16, np.float32, np.float64]
|
||||||
|
for data in [
|
||||||
|
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category"),
|
||||||
|
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category").cat.remove_categories([1]),
|
||||||
|
pd.Categorical(np.array([0, 1, 2], dtype=dtype)),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
# numeric, has NA-induced cast to float32
|
||||||
|
*[
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.float32,
|
||||||
|
"expected_schema_hint": {"type": "categorical"},
|
||||||
|
"logs": {"level": logging.WARNING, "output": "may lose precision"},
|
||||||
|
}
|
||||||
|
for dtype in [
|
||||||
|
np.int8,
|
||||||
|
np.uint8,
|
||||||
|
np.int16,
|
||||||
|
np.uint16,
|
||||||
|
np.int32,
|
||||||
|
np.uint32,
|
||||||
|
np.int64,
|
||||||
|
np.uint64,
|
||||||
|
np.float16,
|
||||||
|
np.float32,
|
||||||
|
np.float64,
|
||||||
|
]
|
||||||
|
for data in [
|
||||||
|
pd.Series(np.array([0, 1, 2], dtype=dtype), dtype="category").cat.remove_categories([1]),
|
||||||
|
pd.Categorical(np.array([0, 1, 2], dtype=dtype), categories=np.array([0, 1], dtype=dtype)),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
category_ERR_cases = [
|
||||||
|
# catch expected categorical exceptions for Int64(etc) that have large values
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"throws": TypeError,
|
||||||
|
}
|
||||||
|
for data in [
|
||||||
|
pd.Categorical(np.array([np.iinfo(np.int64).min, np.iinfo(np.int64).max], dtype=np.int64)),
|
||||||
|
pd.Categorical(np.array([np.iinfo(np.uint64).min, np.iinfo(np.uint64).max], dtype=np.uint64)),
|
||||||
|
pd.Categorical(np.array([np.iinfo(np.uint32).min, np.iinfo(np.uint32).max], dtype=np.uint32)),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
object_OK_cases = [
|
||||||
|
{
|
||||||
|
"data": data,
|
||||||
|
"expected_encoding_dtype": np.dtype(str),
|
||||||
|
"expected_schema_hint": {"type": "string"},
|
||||||
|
}
|
||||||
|
for data in [
|
||||||
|
np.array(["a", True, 1, [], {}], dtype="object"),
|
||||||
|
pd.Series(["a", True, 1, [], {}], dtype="object"),
|
||||||
|
pd.Index(["a", True, 1, [], {}], dtype="object"),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
err_cases = [
|
||||||
|
{"data": np.array, "throws": TypeError}
|
||||||
|
for data in [
|
||||||
|
np.ones((10,), dtype=np.complex64),
|
||||||
|
np.ones((10,), dtype=np.complex128),
|
||||||
|
np.array([b"foobar"], dtype=np.bytes_),
|
||||||
|
np.ones((10,), dtype=np.void),
|
||||||
|
np.arange("2005-02", "2005-03", dtype="datetime64[D]"),
|
||||||
|
np.arange("2005-02", "2005-03", dtype="datetime64[D]") - np.datetime64("2008-01-01"),
|
||||||
|
[],
|
||||||
|
{},
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
*bool_OK_cases,
|
||||||
|
*int_OK_cases,
|
||||||
|
*float_OK_cases,
|
||||||
|
*numeric_ERR_cases,
|
||||||
|
*string_OK_cases,
|
||||||
|
*category_nonnumeric_OK_cases,
|
||||||
|
*category_numeric_OK_cases,
|
||||||
|
*category_ERR_cases,
|
||||||
|
*object_OK_cases,
|
||||||
|
*err_cases,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@parameterized_class(test_cases)
|
||||||
|
class TestTypeInference(unittest.TestCase, AssertNoLog):
|
||||||
|
def test_type_inference(self):
|
||||||
|
throws = getattr(self, "throws", None)
|
||||||
|
if throws:
|
||||||
|
with self.assertRaises(throws):
|
||||||
|
get_dtype_and_schema_of_array(self.data)
|
||||||
|
with self.assertRaises(throws):
|
||||||
|
get_encoding_dtype_of_array(self.data)
|
||||||
|
with self.assertRaises(throws):
|
||||||
|
get_schema_type_hint_of_array(self.data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logs = getattr(self, "logs", None)
|
||||||
|
if logs is not None:
|
||||||
|
with self.assertLogs(level=logs["level"]) as logger:
|
||||||
|
encoding_dtype, schema_hint = get_dtype_and_schema_of_array(self.data)
|
||||||
|
self.assertEqual(encoding_dtype, self.expected_encoding_dtype)
|
||||||
|
self.assertEqual(schema_hint, self.expected_schema_hint)
|
||||||
|
self.assertIn(logs["output"], logger.output[0])
|
||||||
|
|
||||||
|
else:
|
||||||
|
with self.assertNoLogs(logging.getLogger(), logging.WARNING):
|
||||||
|
encoding_dtype, schema_hint = get_dtype_and_schema_of_array(self.data)
|
||||||
|
self.assertEqual(encoding_dtype, self.expected_encoding_dtype)
|
||||||
|
self.assertEqual(schema_hint, self.expected_schema_hint)
|
||||||
|
|
||||||
|
# also test the other public API
|
||||||
|
self.assertEqual(get_encoding_dtype_of_array(self.data), self.expected_encoding_dtype)
|
||||||
|
self.assertEqual(get_schema_type_hint_of_array(self.data), self.expected_schema_hint)
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class ConfigTests(BaseTest):
|
|||||||
environment=None,
|
environment=None,
|
||||||
aws_secrets_manager_region=None,
|
aws_secrets_manager_region=None,
|
||||||
aws_secrets_manager_secrets=[],
|
aws_secrets_manager_secrets=[],
|
||||||
X_approx_distribution="normal",
|
X_approximate_distribution="normal",
|
||||||
config_file_name="app_config.yml",
|
config_file_name="app_config.yml",
|
||||||
):
|
):
|
||||||
random_num = random.randrange(999999)
|
random_num = random.randrange(999999)
|
||||||
@@ -195,7 +195,7 @@ class ConfigTests(BaseTest):
|
|||||||
enable_difexp=enable_difexp,
|
enable_difexp=enable_difexp,
|
||||||
lfc_cutoff=lfc_cutoff,
|
lfc_cutoff=lfc_cutoff,
|
||||||
top_n=top_n,
|
top_n=top_n,
|
||||||
X_approx_distribution=X_approx_distribution,
|
X_approximate_distribution=X_approximate_distribution,
|
||||||
config_file_name=f"temp_dataset_config_{random_num}.yml",
|
config_file_name=f"temp_dataset_config_{random_num}.yml",
|
||||||
)
|
)
|
||||||
external_config = self.custom_external_config(
|
external_config = self.custom_external_config(
|
||||||
@@ -231,7 +231,7 @@ class ConfigTests(BaseTest):
|
|||||||
enable_difexp="true",
|
enable_difexp="true",
|
||||||
lfc_cutoff=0.01,
|
lfc_cutoff=0.01,
|
||||||
top_n=10,
|
top_n=10,
|
||||||
X_approx_distribution="normal",
|
X_approximate_distribution="normal",
|
||||||
config_file_name="dataset_config.yml",
|
config_file_name="dataset_config.yml",
|
||||||
):
|
):
|
||||||
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)
|
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ class EndPointsCxg(EndPoints):
|
|||||||
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'},
|
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'},
|
||||||
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'},
|
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'},
|
||||||
{
|
{
|
||||||
'genes': [{'gene_description': '', 'gene_symbol': 'RER1'}],
|
'genes': [],
|
||||||
'geneset_description': '',
|
'geneset_description': '',
|
||||||
'geneset_name': 'fill_this_geneset'
|
'geneset_name': 'fill_this_geneset'
|
||||||
},
|
},
|
||||||
@@ -485,7 +485,7 @@ summary test,,F5,\r
|
|||||||
summary test,,PIGU,\r
|
summary test,,PIGU,\r
|
||||||
geneset_to_delete,,,\r
|
geneset_to_delete,,,\r
|
||||||
geneset_to_edit,,,\r
|
geneset_to_edit,,,\r
|
||||||
fill_this_geneset,,RER1,\r
|
fill_this_geneset,,,\r
|
||||||
empty_this_geneset,,SIK1,\r
|
empty_this_geneset,,SIK1,\r
|
||||||
brush_this_gene,,SIK1,\r
|
brush_this_gene,,SIK1,\r
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class DiffExpTest(unittest.TestCase):
|
|||||||
adaptor types and different algorithms."""
|
adaptor types and different algorithms."""
|
||||||
|
|
||||||
def load_dataset(self, path, extra_server_config={}, extra_dataset_config={}):
|
def load_dataset(self, path, extra_server_config={}, extra_dataset_config={}):
|
||||||
extra_dataset_config["X_approx_distribution"] = "normal" # hardwired for now
|
extra_dataset_config["X_approximate_distribution"] = "normal" # hardwired for now
|
||||||
config = app_config(path, extra_server_config=extra_server_config, extra_dataset_config=extra_dataset_config)
|
config = app_config(path, extra_server_config=extra_server_config, extra_dataset_config=extra_dataset_config)
|
||||||
loader = MatrixDataLoader(path)
|
loader = MatrixDataLoader(path)
|
||||||
adaptor = loader.open(config)
|
adaptor = loader.open(config)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class FbsTests(unittest.TestCase):
|
|||||||
"d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"),
|
"d": pd.Series(["x", "y", "z", "x", "y", "z", "a", "x", "y", "z"], dtype="category"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.uint32), (list, None))
|
expected_types = ((np.ndarray, np.float32), (np.ndarray, np.int32), (np.ndarray, np.int32), (list, None))
|
||||||
fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns)
|
fbs = encode_matrix_fbs(matrix=df, row_idx=None, col_idx=df.columns)
|
||||||
self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"])
|
self.fbs_checks(fbs, (10, 4), expected_types, ["a", "b", "c", "d"])
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class ConfigTests(unittest.TestCase):
|
|||||||
environment=None,
|
environment=None,
|
||||||
aws_secrets_manager_region=None,
|
aws_secrets_manager_region=None,
|
||||||
aws_secrets_manager_secrets=[],
|
aws_secrets_manager_secrets=[],
|
||||||
X_approx_distribution="auto",
|
X_approximate_distribution="auto",
|
||||||
config_file_name="app_config.yml",
|
config_file_name="app_config.yml",
|
||||||
):
|
):
|
||||||
random_num = random.randrange(999999)
|
random_num = random.randrange(999999)
|
||||||
@@ -151,7 +151,7 @@ class ConfigTests(unittest.TestCase):
|
|||||||
enable_difexp=enable_difexp,
|
enable_difexp=enable_difexp,
|
||||||
lfc_cutoff=lfc_cutoff,
|
lfc_cutoff=lfc_cutoff,
|
||||||
top_n=top_n,
|
top_n=top_n,
|
||||||
X_approx_distribution=X_approx_distribution,
|
X_approximate_distribution=X_approximate_distribution,
|
||||||
config_file_name=f"temp_dataset_config_{random_num}.yml",
|
config_file_name=f"temp_dataset_config_{random_num}.yml",
|
||||||
)
|
)
|
||||||
external_config = self.custom_external_config(
|
external_config = self.custom_external_config(
|
||||||
@@ -187,7 +187,7 @@ class ConfigTests(unittest.TestCase):
|
|||||||
enable_difexp="true",
|
enable_difexp="true",
|
||||||
lfc_cutoff=0.01,
|
lfc_cutoff=0.01,
|
||||||
top_n=10,
|
top_n=10,
|
||||||
X_approx_distribution="auto",
|
X_approximate_distribution="auto",
|
||||||
config_file_name="dataset_config.yml",
|
config_file_name="dataset_config.yml",
|
||||||
):
|
):
|
||||||
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)
|
configfile = os.path.join(self.tmp_fixtures_directory, config_file_name)
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ from http import HTTPStatus
|
|||||||
import tempfile
|
import tempfile
|
||||||
from os import path
|
from os import path
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from os.path import basename, splitext
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from parameterized import parameterized_class
|
||||||
|
|
||||||
import backend.test.decode_fbs as decode_fbs
|
import backend.test.decode_fbs as decode_fbs
|
||||||
from backend.server.data_common.matrix_loader import MatrixDataType
|
from backend.server.data_common.matrix_loader import MatrixDataType
|
||||||
@@ -44,6 +48,14 @@ class EndPoints(object):
|
|||||||
len(result_data["schema"]["annotations"]["obs"]["columns"]), 6 if self.ANNOTATIONS_ENABLED else 5
|
len(result_data["schema"]["annotations"]["obs"]["columns"]), 6 if self.ANNOTATIONS_ENABLED else 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check that all schema types are legal
|
||||||
|
legal_types = ["boolean", "string", "categorical", "float32", "int32"]
|
||||||
|
self.assertEqual(result_data["schema"]["dataframe"]["type"], "float32")
|
||||||
|
for column in result_data["schema"]["annotations"]["obs"]["columns"]:
|
||||||
|
self.assertIn(column["type"], legal_types)
|
||||||
|
for column in result_data["schema"]["annotations"]["var"]["columns"]:
|
||||||
|
self.assertIn(column["type"], legal_types)
|
||||||
|
|
||||||
def test_config(self):
|
def test_config(self):
|
||||||
endpoint = "config"
|
endpoint = "config"
|
||||||
url = f"{self.URL_BASE}{endpoint}"
|
url = f"{self.URL_BASE}{endpoint}"
|
||||||
@@ -52,7 +64,12 @@ class EndPoints(object):
|
|||||||
self.assertEqual(result.headers["Content-Type"], "application/json")
|
self.assertEqual(result.headers["Content-Type"], "application/json")
|
||||||
result_data = result.json()
|
result_data = result.json()
|
||||||
self.assertIn("library_versions", result_data["config"])
|
self.assertIn("library_versions", result_data["config"])
|
||||||
self.assertEqual(result_data["config"]["displayNames"]["dataset"], "pbmc3k")
|
|
||||||
|
if hasattr(self, "data_locator"):
|
||||||
|
title = splitext(basename(self.data_locator))[0]
|
||||||
|
else:
|
||||||
|
title = "pbmc3k"
|
||||||
|
self.assertEqual(result_data["config"]["displayNames"]["dataset"], title)
|
||||||
self.assertIsNotNone(result_data["config"]["parameters"])
|
self.assertIsNotNone(result_data["config"]["parameters"])
|
||||||
|
|
||||||
def test_get_layout_fbs(self):
|
def test_get_layout_fbs(self):
|
||||||
@@ -72,6 +89,8 @@ class EndPoints(object):
|
|||||||
)
|
)
|
||||||
self.assertIsNone(df["row_idx"])
|
self.assertIsNone(df["row_idx"])
|
||||||
self.assertEqual(len(df["columns"]), df["n_cols"])
|
self.assertEqual(len(df["columns"]), df["n_cols"])
|
||||||
|
for column in df["columns"]:
|
||||||
|
self.assertEqual(column.dtype, np.float32)
|
||||||
|
|
||||||
def test_bad_filter(self):
|
def test_bad_filter(self):
|
||||||
endpoint = "data/var"
|
endpoint = "data/var"
|
||||||
@@ -98,6 +117,9 @@ class EndPoints(object):
|
|||||||
[obs_index_col_name, "n_genes", "percent_mito", "n_counts", "louvain"]
|
[obs_index_col_name, "n_genes", "percent_mito", "n_counts", "louvain"]
|
||||||
+ (["cluster-test"] if self.ANNOTATIONS_ENABLED else []),
|
+ (["cluster-test"] if self.ANNOTATIONS_ENABLED else []),
|
||||||
)
|
)
|
||||||
|
for column in df["columns"]:
|
||||||
|
if type(column) is np.ndarray:
|
||||||
|
self.assertIn(column.dtype, [np.float32, np.int32])
|
||||||
|
|
||||||
def test_get_annotations_obs_keys_fbs(self):
|
def test_get_annotations_obs_keys_fbs(self):
|
||||||
endpoint = "annotations/obs"
|
endpoint = "annotations/obs"
|
||||||
@@ -137,6 +159,9 @@ class EndPoints(object):
|
|||||||
self.assertEqual(len(df["columns"]), df["n_cols"])
|
self.assertEqual(len(df["columns"]), df["n_cols"])
|
||||||
var_index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
var_index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
||||||
self.assertCountEqual(df["col_idx"], [var_index_col_name, "n_cells"])
|
self.assertCountEqual(df["col_idx"], [var_index_col_name, "n_cells"])
|
||||||
|
for column in df["columns"]:
|
||||||
|
if type(column) is np.ndarray:
|
||||||
|
self.assertIn(column.dtype, [np.float32, np.int32])
|
||||||
|
|
||||||
def test_get_annotations_var_keys_fbs(self):
|
def test_get_annotations_var_keys_fbs(self):
|
||||||
endpoint = "annotations/var"
|
endpoint = "annotations/var"
|
||||||
@@ -207,6 +232,9 @@ class EndPoints(object):
|
|||||||
self.assertIsNone(df["row_idx"])
|
self.assertIsNone(df["row_idx"])
|
||||||
self.assertEqual(len(df["columns"]), df["n_cols"])
|
self.assertEqual(len(df["columns"]), df["n_cols"])
|
||||||
self.assertListEqual(df["col_idx"].tolist(), [0, 1, 4])
|
self.assertListEqual(df["col_idx"].tolist(), [0, 1, 4])
|
||||||
|
for column in df["columns"]:
|
||||||
|
if type(column) is np.ndarray:
|
||||||
|
self.assertIn(column.dtype, [np.float32, np.int32])
|
||||||
|
|
||||||
def test_data_get_filter_fbs(self):
|
def test_data_get_filter_fbs(self):
|
||||||
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
||||||
@@ -220,6 +248,9 @@ class EndPoints(object):
|
|||||||
df = decode_fbs.decode_matrix_FBS(result.content)
|
df = decode_fbs.decode_matrix_FBS(result.content)
|
||||||
self.assertEqual(df["n_rows"], 2638)
|
self.assertEqual(df["n_rows"], 2638)
|
||||||
self.assertEqual(df["n_cols"], 1)
|
self.assertEqual(df["n_cols"], 1)
|
||||||
|
for column in df["columns"]:
|
||||||
|
if type(column) is np.ndarray:
|
||||||
|
self.assertIn(column.dtype, [np.float32, np.int32])
|
||||||
|
|
||||||
def test_data_get_unknown_filter_fbs(self):
|
def test_data_get_unknown_filter_fbs(self):
|
||||||
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
||||||
@@ -246,6 +277,9 @@ class EndPoints(object):
|
|||||||
df = decode_fbs.decode_matrix_FBS(result.content)
|
df = decode_fbs.decode_matrix_FBS(result.content)
|
||||||
self.assertEqual(df["n_rows"], 2638)
|
self.assertEqual(df["n_rows"], 2638)
|
||||||
self.assertEqual(df["n_cols"], 1)
|
self.assertEqual(df["n_cols"], 1)
|
||||||
|
for column in df["columns"]:
|
||||||
|
if type(column) is np.ndarray:
|
||||||
|
self.assertIn(column.dtype, [np.float32, np.int32])
|
||||||
|
|
||||||
def test_colors(self):
|
def test_colors(self):
|
||||||
endpoint = "colors"
|
endpoint = "colors"
|
||||||
@@ -347,6 +381,14 @@ class EndPointsAnnotations(EndPoints):
|
|||||||
self.assertTrue(matching_columns[0]["writable"])
|
self.assertTrue(matching_columns[0]["writable"])
|
||||||
|
|
||||||
|
|
||||||
|
@parameterized_class(
|
||||||
|
[
|
||||||
|
{"data_locator": f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad"},
|
||||||
|
{"data_locator": f"{FIXTURES_ROOT}/pbmc3k_64.h5ad"},
|
||||||
|
{"data_locator": f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad"},
|
||||||
|
{"data_locator": f"{FIXTURES_ROOT}/pbmc3k-CSR-gz.h5ad"},
|
||||||
|
]
|
||||||
|
)
|
||||||
class EndPointsAnndata(unittest.TestCase, EndPoints):
|
class EndPointsAnndata(unittest.TestCase, EndPoints):
|
||||||
"""Test Case for endpoints"""
|
"""Test Case for endpoints"""
|
||||||
|
|
||||||
@@ -355,10 +397,13 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
if cls == EndPointsAnndata:
|
||||||
|
raise unittest.SkipTest("`parameterized_class` bug")
|
||||||
|
|
||||||
cls._setupClass(
|
cls._setupClass(
|
||||||
cls,
|
cls,
|
||||||
[
|
[
|
||||||
f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad",
|
cls.data_locator,
|
||||||
"--disable-annotations",
|
"--disable-annotations",
|
||||||
"--disable-gene-sets-save",
|
"--disable-gene-sets-save",
|
||||||
],
|
],
|
||||||
@@ -385,8 +430,8 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
|
|||||||
self.assertEqual(result.status_code, HTTPStatus.OK)
|
self.assertEqual(result.status_code, HTTPStatus.OK)
|
||||||
self.assertEqual(result.headers["Content-Type"], "application/json")
|
self.assertEqual(result.headers["Content-Type"], "application/json")
|
||||||
result_data = result.json()
|
result_data = result.json()
|
||||||
self.assertEqual(len(result_data['positive']), 7)
|
self.assertEqual(len(result_data["positive"]), 7)
|
||||||
self.assertEqual(len(result_data['negative']), 7)
|
self.assertEqual(len(result_data["negative"]), 7)
|
||||||
|
|
||||||
def test_diff_exp_indices(self):
|
def test_diff_exp_indices(self):
|
||||||
endpoint = "diffexp/obs"
|
endpoint = "diffexp/obs"
|
||||||
@@ -401,8 +446,8 @@ class EndPointsAnndata(unittest.TestCase, EndPoints):
|
|||||||
self.assertEqual(result.status_code, HTTPStatus.OK)
|
self.assertEqual(result.status_code, HTTPStatus.OK)
|
||||||
self.assertEqual(result.headers["Content-Type"], "application/json")
|
self.assertEqual(result.headers["Content-Type"], "application/json")
|
||||||
result_data = result.json()
|
result_data = result.json()
|
||||||
self.assertEqual(len(result_data['positive']), 10)
|
self.assertEqual(len(result_data["positive"]), 10)
|
||||||
self.assertEqual(len(result_data['negative']), 10)
|
self.assertEqual(len(result_data["negative"]), 10)
|
||||||
|
|
||||||
def test_get_summaryvar(self):
|
def test_get_summaryvar(self):
|
||||||
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
index_col_name = self.schema["schema"]["annotations"]["var"]["index"]
|
||||||
@@ -561,23 +606,23 @@ class EndPointsAnnDataGenesets(unittest.TestCase, EndPoints):
|
|||||||
"geneset_description": "",
|
"geneset_description": "",
|
||||||
"geneset_name": "summary test",
|
"geneset_name": "summary test",
|
||||||
},
|
},
|
||||||
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_delete'},
|
{"genes": [], "geneset_description": "", "geneset_name": "geneset_to_delete"},
|
||||||
{'genes': [], 'geneset_description': '', 'geneset_name': 'geneset_to_edit'},
|
{"genes": [], "geneset_description": "", "geneset_name": "geneset_to_edit"},
|
||||||
{
|
{
|
||||||
'genes': [{'gene_description': '', 'gene_symbol': 'RER1'}],
|
"genes": [],
|
||||||
'geneset_description': '',
|
"geneset_description": "",
|
||||||
'geneset_name': 'fill_this_geneset'
|
"geneset_name": "fill_this_geneset",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'genes': [{'gene_description': '', 'gene_symbol': 'SIK1'}],
|
"genes": [{"gene_description": "", "gene_symbol": "SIK1"}],
|
||||||
'geneset_description': '',
|
"geneset_description": "",
|
||||||
'geneset_name': 'empty_this_geneset'
|
"geneset_name": "empty_this_geneset",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'genes': [{'gene_description': '', 'gene_symbol': 'SIK1'}],
|
"genes": [{"gene_description": "", "gene_symbol": "SIK1"}],
|
||||||
'geneset_description': '',
|
"geneset_description": "",
|
||||||
'geneset_name': 'brush_this_gene'
|
"geneset_name": "brush_this_gene",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"tid": 0,
|
"tid": 0,
|
||||||
},
|
},
|
||||||
@@ -606,7 +651,7 @@ summary test,,F5,\r
|
|||||||
summary test,,PIGU,\r
|
summary test,,PIGU,\r
|
||||||
geneset_to_delete,,,\r
|
geneset_to_delete,,,\r
|
||||||
geneset_to_edit,,,\r
|
geneset_to_edit,,,\r
|
||||||
fill_this_geneset,,RER1,\r
|
fill_this_geneset,,,\r
|
||||||
empty_this_geneset,,SIK1,\r
|
empty_this_geneset,,SIK1,\r
|
||||||
brush_this_gene,,SIK1,\r
|
brush_this_gene,,SIK1,\r
|
||||||
""",
|
""",
|
||||||
@@ -680,7 +725,7 @@ brush_this_gene,,SIK1,\r
|
|||||||
self.assertEqual(result.json(), test3)
|
self.assertEqual(result.json(), test3)
|
||||||
|
|
||||||
def test_put_genesets_malformed(self):
|
def test_put_genesets_malformed(self):
|
||||||
""" test malformed submissions that we expect the backend to catch/tolerate """
|
"""test malformed submissions that we expect the backend to catch/tolerate"""
|
||||||
endpoint = "genesets"
|
endpoint = "genesets"
|
||||||
url = f"{self.URL_BASE}{endpoint}"
|
url = f"{self.URL_BASE}{endpoint}"
|
||||||
|
|
||||||
@@ -690,7 +735,7 @@ brush_this_gene,,SIK1,\r
|
|||||||
tid = original_data["tid"]
|
tid = original_data["tid"]
|
||||||
|
|
||||||
def test_case(test, expected_code, original_data):
|
def test_case(test, expected_code, original_data):
|
||||||
""" check for expected error AND that no change was made to the original state """
|
"""check for expected error AND that no change was made to the original state"""
|
||||||
result = self.session.put(url, json=test)
|
result = self.session.put(url, json=test)
|
||||||
self.assertEqual(result.status_code, expected_code)
|
self.assertEqual(result.status_code, expected_code)
|
||||||
result = self.session.get(url, headers={"Accept": "application/json"})
|
result = self.session.get(url, headers={"Accept": "application/json"})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import unittest
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy import sparse
|
from scipy import sparse
|
||||||
from backend.common.compute.estimate_distribution import estimate_approximate_distribution
|
from backend.common.compute.estimate_distribution import estimate_approximate_distribution
|
||||||
from backend.common.constants import XApproxDistribution
|
from backend.common.constants import XApproximateDistribution
|
||||||
from backend.server.data_common.matrix_loader import MatrixDataLoader
|
from backend.server.data_common.matrix_loader import MatrixDataLoader
|
||||||
from backend.test.test_server.unit import app_config
|
from backend.test.test_server.unit import app_config
|
||||||
from backend.test import PROJECT_ROOT
|
from backend.test import PROJECT_ROOT
|
||||||
@@ -20,22 +20,101 @@ class EstDistTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_adaptestimate_approximate_distribution(self):
|
def test_adaptestimate_approximate_distribution(self):
|
||||||
adaptor = self.load_dataset(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad")
|
adaptor = self.load_dataset(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad")
|
||||||
self.assertEqual(adaptor.get_X_approx_distribution(), XApproxDistribution.NORMAL)
|
self.assertEqual(adaptor.get_X_approximate_distribution(), XApproximateDistribution.NORMAL)
|
||||||
|
|
||||||
def test_estimate_approximate_distribution(self):
|
def test_estimate_approximate_distribution(self):
|
||||||
raw = np.random.exponential(scale=1000, size=(100, 40))
|
raw = np.random.exponential(scale=1000, size=(100, 40))
|
||||||
|
|
||||||
|
# empty
|
||||||
|
self.assertEqual(estimate_approximate_distribution(np.zeros((0,))), XApproximateDistribution.NORMAL)
|
||||||
|
|
||||||
# ndarray
|
# ndarray
|
||||||
self.assertEqual(estimate_approximate_distribution(raw), XApproxDistribution.COUNT)
|
self.assertEqual(estimate_approximate_distribution(raw), XApproximateDistribution.COUNT)
|
||||||
self.assertEqual(estimate_approximate_distribution(np.log1p(raw)), XApproxDistribution.NORMAL)
|
self.assertEqual(estimate_approximate_distribution(np.log1p(raw)), XApproximateDistribution.NORMAL)
|
||||||
|
|
||||||
# csr_matrix
|
# csr_matrix
|
||||||
self.assertEqual(estimate_approximate_distribution(sparse.csr_matrix(raw)), XApproxDistribution.COUNT)
|
self.assertEqual(estimate_approximate_distribution(sparse.csr_matrix(raw)), XApproximateDistribution.COUNT)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
estimate_approximate_distribution(sparse.csr_matrix(np.log1p(raw))), XApproxDistribution.NORMAL
|
estimate_approximate_distribution(sparse.csr_matrix(np.log1p(raw))), XApproximateDistribution.NORMAL
|
||||||
|
)
|
||||||
|
|
||||||
|
# csc_matrix
|
||||||
|
self.assertEqual(estimate_approximate_distribution(sparse.csc_matrix(raw)), XApproximateDistribution.COUNT)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(sparse.csc_matrix(np.log1p(raw))), XApproximateDistribution.NORMAL
|
||||||
)
|
)
|
||||||
|
|
||||||
# BIG (ie, trigger MT)
|
# BIG (ie, trigger MT)
|
||||||
big = np.random.exponential(scale=100, size=(1_000_000, 100))
|
big = np.random.exponential(scale=100, size=(1_000_000, 100))
|
||||||
self.assertEqual(estimate_approximate_distribution(big), XApproxDistribution.COUNT)
|
self.assertEqual(estimate_approximate_distribution(big), XApproximateDistribution.COUNT)
|
||||||
self.assertEqual(estimate_approximate_distribution(np.log1p(big)), XApproxDistribution.NORMAL)
|
self.assertEqual(estimate_approximate_distribution(np.log1p(big)), XApproximateDistribution.NORMAL)
|
||||||
|
|
||||||
|
def test_unsupported_throws(self):
|
||||||
|
# dtypes and matrix formats we do not support
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
estimate_approximate_distribution(np.array(["a", "b"]))
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
estimate_approximate_distribution(sparse.coo_matrix(np.array([[0, 1, 2], [3, 0, 2]])))
|
||||||
|
|
||||||
|
def test_nonfinites(self):
|
||||||
|
def put(arr, ind, vals):
|
||||||
|
# like np.put, but creates and returns a modified copy of original array
|
||||||
|
a = arr.copy()
|
||||||
|
np.put(a, ind, vals)
|
||||||
|
return a
|
||||||
|
|
||||||
|
# non-finites
|
||||||
|
self.assertEqual(estimate_approximate_distribution(np.array([np.nan])), XApproximateDistribution.NORMAL)
|
||||||
|
self.assertEqual(estimate_approximate_distribution(np.array([np.PINF])), XApproximateDistribution.NORMAL)
|
||||||
|
self.assertEqual(estimate_approximate_distribution(np.array([np.NINF])), XApproximateDistribution.NORMAL)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(np.array([np.PINF, np.NINF, 0])), XApproximateDistribution.NORMAL
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(np.array([np.nan, np.PINF, np.NINF])), XApproximateDistribution.NORMAL
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = np.random.exponential(scale=1000, size=(50, 3))
|
||||||
|
logged = np.log1p(raw)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(raw, [1], [np.nan])),
|
||||||
|
XApproximateDistribution.COUNT,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(raw, [1], [np.PINF])),
|
||||||
|
XApproximateDistribution.COUNT,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(raw, [1], [np.NINF])),
|
||||||
|
XApproximateDistribution.COUNT,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(raw, [1, 3, 88], [np.nan, np.PINF, np.NINF])),
|
||||||
|
XApproximateDistribution.COUNT,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(raw, [0, 1], [np.nan, np.nan])),
|
||||||
|
XApproximateDistribution.COUNT,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(logged, [1], [np.nan])),
|
||||||
|
XApproximateDistribution.NORMAL,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(logged, [1], [np.PINF])),
|
||||||
|
XApproximateDistribution.NORMAL,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(logged, [1], [np.NINF])),
|
||||||
|
XApproximateDistribution.NORMAL,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(logged, [1, 3, 88], [np.nan, np.PINF, np.NINF])),
|
||||||
|
XApproximateDistribution.NORMAL,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
estimate_approximate_distribution(put(logged, [0, 1], [np.nan, np.nan])),
|
||||||
|
XApproximateDistribution.NORMAL,
|
||||||
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Test the anndata adaptor using the pbmc3k data set.
|
|||||||
|
|
||||||
|
|
||||||
@parameterized_class(
|
@parameterized_class(
|
||||||
("data_locator", "backed", "X_approx_distribution"),
|
("data_locator", "backed", "X_approximate_distribution"),
|
||||||
[
|
[
|
||||||
(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", False, "auto"),
|
(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", False, "auto"),
|
||||||
(f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", False, "auto"),
|
(f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", False, "auto"),
|
||||||
@@ -36,12 +36,15 @@ Test the anndata adaptor using the pbmc3k data set.
|
|||||||
(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", True, "normal"),
|
(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", True, "normal"),
|
||||||
(f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", True, "normal"),
|
(f"{FIXTURES_ROOT}/pbmc3k-CSC-gz.h5ad", True, "normal"),
|
||||||
(f"{FIXTURES_ROOT}/pbmc3k-CSR-gz.h5ad", True, "normal"),
|
(f"{FIXTURES_ROOT}/pbmc3k-CSR-gz.h5ad", True, "normal"),
|
||||||
|
(f"{FIXTURES_ROOT}/pbmc3k_64.h5ad", False, "auto"), # 64 bit conversion tests
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class AdaptorTest(unittest.TestCase):
|
class AdaptorTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
config = app_config(
|
config = app_config(
|
||||||
self.data_locator, self.backed, extra_dataset_config=dict(X_approx_distribution=self.X_approx_distribution)
|
self.data_locator,
|
||||||
|
self.backed,
|
||||||
|
extra_dataset_config=dict(X_approximate_distribution=self.X_approximate_distribution),
|
||||||
)
|
)
|
||||||
self.data = AnndataAdaptor(DataLocator(self.data_locator), config)
|
self.data = AnndataAdaptor(DataLocator(self.data_locator), config)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
exports[`did launch page launched 1`] = `"<span style=\\"max-width: 155px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`;
|
exports[`did launch page launched 1`] = `"<span style=\\"max-width: 155px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">pbm</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">c3k</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">c3k</span></span></span>"`;
|
||||||
|
|
||||||
exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover2-target\\"><span data-testid=\\"louvain:category-label\\" tabindex=\\"-1\\" aria-label=\\"louvain\\" class=\\"\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div><div></div>"`;
|
exports[`metadata loads categories and values from dataset appear 1`] = `"<div style=\\"display: flex; justify-content: space-between; align-items: baseline;\\"><div style=\\"display: flex; justify-content: flex-start; align-items: flex-start;\\"><label class=\\"bp3-control bp3-checkbox\\" for=\\"category-select-louvain\\"><input id=\\"category-select-louvain\\" data-testclass=\\"category-select\\" data-testid=\\"louvain:category-select\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span role=\\"menuitem\\" tabindex=\\"0\\" data-testclass=\\"category-expand\\" data-testid=\\"louvain:category-expand\\" style=\\"cursor: pointer;\\"><span class=\\"bp3-popover2-target\\"><span data-testid=\\"louvain:category-label\\" tabindex=\\"-1\\" aria-label=\\"louvain\\" class=\\"\\" style=\\"max-width: 265px;\\"><span style=\\"max-width: 265px; display: flex; overflow: hidden; justify-content: flex-start; width: 100%; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">lou</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">vain</span><span style=\\"position: absolute; right: 0px; color: inherit;\\">vain</span></span></span></span></span><svg stroke=\\"currentColor\\" fill=\\"currentColor\\" stroke-width=\\"0\\" viewBox=\\"0 0 320 512\\" data-testclass=\\"category-expand-is-not-expanded\\" height=\\"1em\\" width=\\"1em\\" xmlns=\\"http://www.w3.org/2000/svg\\" style=\\"font-size: 10px; margin-left: 5px;\\"><path d=\\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\\"></path></svg></span></div><div><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><a role=\\"button\\" data-testclass=\\"colorby\\" data-testid=\\"colorby-louvain\\" class=\\"bp3-button\\" tabindex=\\"0\\"><span icon=\\"tint\\" class=\\"bp3-icon bp3-icon-tint\\"><svg data-icon=\\"tint\\" width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\"><desc>tint</desc><path d=\\"M7.88 1s-4.9 6.28-4.9 8.9c.01 2.82 2.34 5.1 4.99 5.1 2.65-.01 5.03-2.3 5.03-5.13C12.99 7.17 7.88 1 7.88 1z\\" fill-rule=\\"evenodd\\"></path></svg></span></a></span></span></div></div><div style=\\"margin-left: 26px;\\"></div>"`;
|
||||||
+4
-4
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
exports[`annotations stacked bar graph renders 1`] = `
|
exports[`annotations stacked bar graph renders 1`] = `
|
||||||
Array [
|
Array [
|
||||||
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2133</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2133</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`annotations stacked bar graph renders 2`] = `
|
exports[`annotations stacked bar graph renders 2`] = `
|
||||||
Array [
|
Array [
|
||||||
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-TEST-LABEL\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-TEST-LABEL\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"TEST-LABEL\\" class=\\"\\" style=\\"width: 63px; color: black; font-style: normal; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: black; font-style: normal; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">TEST-</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">LABEL</span><span style=\\"position: absolute; right: 0px; color: black;\\">LABEL</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-TEST-LABEL\\" style=\\"color: black;\\">0</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:TEST-LABEL:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
"<div class=\\"categorical__value___2m6V7\\" data-testclass=\\"categorical-row\\" style=\\"padding: 4px 0px 4px 7px; display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 2px; border-radius: 2px;\\"><div style=\\"margin: 0px; padding: 0px; user-select: none; width: 220px; display: flex; justify-content: space-between;\\"><div style=\\"display: flex; align-items: baseline;\\"><label for=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" class=\\"bp3-control bp3-checkbox\\" style=\\"margin: 0px;\\"><input id=\\"value-toggle-checkbox-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value-select\\" data-testid=\\"categorical-value-select-TEST-CATEGORY-unassigned\\" type=\\"checkbox\\" checked=\\"\\"><span class=\\"bp3-control-indicator\\"></span></label><span class=\\"bp3-popover2-target\\"><span data-testid=\\"categorical-value-TEST-CATEGORY-unassigned\\" data-testclass=\\"categorical-value\\" tabindex=\\"-1\\" aria-label=\\"unassigned\\" class=\\"\\" style=\\"width: 63px; color: rgb(171, 171, 171); font-style: italic; display: inline-block; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px;\\"><span style=\\"width: 100%; color: rgb(171, 171, 171); font-style: italic; display: flex; overflow: hidden; line-height: 1.1em; height: 1.1em; vertical-align: middle; margin-right: 16px; justify-content: flex-start; padding: 0px;\\"><span style=\\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-shrink: 1; min-width: 5px;\\">unass</span><span style=\\"position: relative; overflow: hidden; white-space: nowrap;\\"><span style=\\"color: transparent;\\">igned</span><span style=\\"position: absolute; right: 0px; color: rgb(171, 171, 171);\\">igned</span></span></span></span></span></div><span style=\\"flex-shrink: 0;\\"><canvas width=\\"100\\" height=\\"11\\" style=\\"margin-right: 5px; width: 100px; height: 11px;\\"></canvas></span></div><div><span><span data-testclass=\\"categorical-value-count\\" data-testid=\\"categorical-value-count-TEST-CATEGORY-unassigned\\" style=\\"color: rgb(171, 171, 171); font-style: italic;\\">2638</span><svg display=\\"none\\" style=\\"margin-left: 5px; width: 15px; height: 15px; background-color: inherit;\\"></svg><span><span class=\\"bp3-popover-wrapper\\"><span aria-haspopup=\\"true\\" class=\\"bp3-popover-target\\"><button type=\\"button\\" data-testclass=\\"seeActions\\" data-testid=\\"TEST-CATEGORY:unassigned:see-actions\\" class=\\"bp3-button bp3-minimal bp3-small\\" tabindex=\\"0\\" style=\\"margin-left: 2px; position: relative; top: -1px; min-height: 16px;\\"><span icon=\\"more\\" class=\\"bp3-icon bp3-icon-more\\"><svg data-icon=\\"more\\" width=\\"10\\" height=\\"10\\" viewBox=\\"0 0 16 16\\"><desc>more</desc><path d=\\"M2 6.03a2 2 0 100 4 2 2 0 100-4zM14 6.03a2 2 0 100 4 2 2 0 100-4zM8 6.03a2 2 0 100 4 2 2 0 100-4z\\" fill-rule=\\"evenodd\\"></path></svg></span></button></span></span></span></span></div></div>",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -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 */
|
|
||||||
@@ -0,0 +1,596 @@
|
|||||||
|
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|
||||||
|
import { strict as assert } from "assert";
|
||||||
|
import {
|
||||||
|
clearInputAndTypeInto,
|
||||||
|
clickOn,
|
||||||
|
getAllByClass,
|
||||||
|
getOneElementInnerText,
|
||||||
|
typeInto,
|
||||||
|
waitByID,
|
||||||
|
waitByClass,
|
||||||
|
waitForAllByIds,
|
||||||
|
clickOnUntil,
|
||||||
|
getTestClass,
|
||||||
|
getTestId,
|
||||||
|
isElementPresent,
|
||||||
|
goToPage,
|
||||||
|
} from "./puppeteerUtils";
|
||||||
|
|
||||||
|
import { appUrlBase, TEST_EMAIL, TEST_PASSWORD } from "./config";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function drag(testId: any, start: any, end: any, lasso = false) {
|
||||||
|
const layout = await waitByID(testId);
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const elBox = await layout.boxModel();
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const x1 = elBox.content[0].x + start.x;
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const x2 = elBox.content[0].x + end.x;
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const y1 = elBox.content[0].y + start.y;
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const y2 = elBox.content[0].y + end.y;
|
||||||
|
await page.mouse.move(x1, y1);
|
||||||
|
await page.mouse.down();
|
||||||
|
if (lasso) {
|
||||||
|
await page.mouse.move(x2, y1);
|
||||||
|
await page.mouse.move(x2, y2);
|
||||||
|
await page.mouse.move(x1, y2);
|
||||||
|
await page.mouse.move(x1, y1);
|
||||||
|
} else {
|
||||||
|
await page.mouse.move(x2, y2);
|
||||||
|
}
|
||||||
|
await page.mouse.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function clickOnCoordinate(testId: any, coord: any) {
|
||||||
|
const layout = await expect(page).toMatchElement(getTestId(testId));
|
||||||
|
const elBox = await layout.boxModel();
|
||||||
|
|
||||||
|
if (!elBox) {
|
||||||
|
throw Error("Layout's boxModel is not available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = elBox.content[0].x + coord.x;
|
||||||
|
const y = elBox.content[0].y + coord.y;
|
||||||
|
await page.mouse.click(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function getAllHistograms(testclass: any, testIds: any) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const histTestIds = testIds.map((tid: any) => `histogram-${tid}`);
|
||||||
|
|
||||||
|
// these load asynchronously, so we need to wait for each histogram individually,
|
||||||
|
// and they may be quite slow in some cases.
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 2.
|
||||||
|
await waitForAllByIds(histTestIds, { timeout: 4 * 60 * 1000 });
|
||||||
|
|
||||||
|
const allHistograms = await getAllByClass(testclass);
|
||||||
|
|
||||||
|
const testIDs = await Promise.all(
|
||||||
|
allHistograms.map((hist) =>
|
||||||
|
page.evaluate((elem) => elem.dataset.testid, hist)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return testIDs.map((id) => id.replace(/^histogram-/, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function getAllCategoriesAndCounts(category: any) {
|
||||||
|
// these load asynchronously, so we have to wait for the specific category.
|
||||||
|
await waitByID(`category-${category}`);
|
||||||
|
|
||||||
|
return page.$$eval(
|
||||||
|
`[data-testid="category-${category}"] [data-testclass='categorical-row']`,
|
||||||
|
(rows) =>
|
||||||
|
Object.fromEntries(
|
||||||
|
rows.map((row) => {
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const cat = row
|
||||||
|
.querySelector("[data-testclass='categorical-value']")
|
||||||
|
.getAttribute("aria-label");
|
||||||
|
|
||||||
|
const count = (row.querySelector(
|
||||||
|
"[data-testclass='categorical-value-count']"
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) as any).innerText;
|
||||||
|
|
||||||
|
return [cat, count];
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function getCellSetCount(num: any) {
|
||||||
|
await clickOn(`cellset-button-${num}`);
|
||||||
|
return getOneElementInnerText(`[data-testid='cellset-count-${num}']`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function resetCategory(category: any) {
|
||||||
|
const checkboxId = `${category}:category-select`;
|
||||||
|
await waitByID(checkboxId);
|
||||||
|
const checkedPseudoclass = await page.$eval(
|
||||||
|
`[data-testid='${checkboxId}']`,
|
||||||
|
(el) => el.matches(":checked")
|
||||||
|
);
|
||||||
|
if (!checkedPseudoclass) await clickOn(checkboxId);
|
||||||
|
|
||||||
|
const categoryRow = await waitByID(`${category}:category-expand`);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const isExpanded = await categoryRow.$(
|
||||||
|
"[data-testclass='category-expand-is-expanded']"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isExpanded) await clickOn(`${category}:category-expand`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function calcCoordinate(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
testId: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
xAsPercent: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
yAsPercent: any
|
||||||
|
) {
|
||||||
|
const el = await waitByID(testId);
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const size = await el.boxModel();
|
||||||
|
return {
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
x: Math.floor(size.width * xAsPercent),
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
y: Math.floor(size.height * yAsPercent),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function calcDragCoordinates(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
testId: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
coordinateAsPercent: any
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
start: await calcCoordinate(
|
||||||
|
testId,
|
||||||
|
coordinateAsPercent.x1,
|
||||||
|
coordinateAsPercent.y1
|
||||||
|
),
|
||||||
|
end: await calcCoordinate(
|
||||||
|
testId,
|
||||||
|
coordinateAsPercent.x2,
|
||||||
|
coordinateAsPercent.y2
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function selectCategory(category: any, values: any, reset = true) {
|
||||||
|
if (reset) await resetCategory(category);
|
||||||
|
|
||||||
|
await clickOn(`${category}:category-expand`);
|
||||||
|
await clickOn(`${category}:category-select`);
|
||||||
|
|
||||||
|
for (const value of values) {
|
||||||
|
await clickOn(`categorical-value-select-${category}-${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function expandCategory(category: any) {
|
||||||
|
const expand = await waitByID(`${category}:category-expand`);
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const notExpanded = await expand.$(
|
||||||
|
"[data-testclass='category-expand-is-not-expanded']"
|
||||||
|
);
|
||||||
|
if (notExpanded) await clickOn(`${category}:category-expand`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function clip(min = 0, max = 100) {
|
||||||
|
await clickOn("visualization-settings");
|
||||||
|
await clearInputAndTypeInto("clip-min-input", min);
|
||||||
|
await clearInputAndTypeInto("clip-max-input", max);
|
||||||
|
await clickOn("clip-commit");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function createCategory(categoryName: any) {
|
||||||
|
await clickOnUntil("open-annotation-dialog", async () => {
|
||||||
|
await expect(page).toMatchElement(getTestId("new-category-name"));
|
||||||
|
});
|
||||||
|
|
||||||
|
await typeInto("new-category-name", categoryName);
|
||||||
|
await clickOn("submit-category");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GENESET
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function colorByGeneset(genesetName: any) {
|
||||||
|
await clickOn(`${genesetName}:colorby-entire-geneset`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function colorByGene(gene: any) {
|
||||||
|
await clickOn(`colorby-${gene}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function assertColorLegendLabel(label: any) {
|
||||||
|
const handle = await waitByID("continuous_legend_color_by_label");
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const result = await handle.evaluate((node) =>
|
||||||
|
node.getAttribute("aria-label")
|
||||||
|
);
|
||||||
|
|
||||||
|
return expect(result).toBe(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function expandGeneset(genesetName: any) {
|
||||||
|
const expand = await waitByID(`${genesetName}:geneset-expand`);
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const notExpanded = await expand.$(
|
||||||
|
"[data-testclass='geneset-expand-is-not-expanded']"
|
||||||
|
);
|
||||||
|
if (notExpanded) await clickOn(`${genesetName}:geneset-expand`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function createGeneset(genesetName: any) {
|
||||||
|
await clickOnUntil("open-create-geneset-dialog", async () => {
|
||||||
|
await expect(page).toMatchElement(getTestId("create-geneset-input"));
|
||||||
|
});
|
||||||
|
|
||||||
|
await typeInto("create-geneset-input", genesetName);
|
||||||
|
await clickOn("submit-geneset");
|
||||||
|
await waitByClass("autosave-complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function editGenesetName(genesetName: any, editText: any) {
|
||||||
|
const editButton = `${genesetName}:edit-genesetName-mode`;
|
||||||
|
const submitButton = `${genesetName}:submit-geneset`;
|
||||||
|
await clickOnUntil(`${genesetName}:see-actions`, async () => {
|
||||||
|
await expect(page).toMatchElement(getTestId(editButton));
|
||||||
|
});
|
||||||
|
await clickOn(editButton);
|
||||||
|
await typeInto("rename-geneset-modal", editText);
|
||||||
|
await clickOn(submitButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function deleteGeneset(genesetName: any) {
|
||||||
|
const targetId = `${genesetName}:delete-geneset`;
|
||||||
|
|
||||||
|
await clickOnUntil(`${genesetName}:see-actions`, async () => {
|
||||||
|
await expect(page).toMatchElement(getTestId(targetId));
|
||||||
|
});
|
||||||
|
|
||||||
|
await clickOn(targetId);
|
||||||
|
|
||||||
|
await assertGenesetDoesNotExist(genesetName);
|
||||||
|
await waitByClass("autosave-complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function assertGenesetDoesNotExist(genesetName: any) {
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
|
const result = await isElementPresent(
|
||||||
|
getTestId(`${genesetName}:geneset-name`)
|
||||||
|
);
|
||||||
|
await expect(result).toBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function assertGenesetExists(genesetName: any) {
|
||||||
|
const handle = await waitByID(`${genesetName}:geneset-name`);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const result = await handle.evaluate((node) =>
|
||||||
|
node.getAttribute("aria-label")
|
||||||
|
);
|
||||||
|
|
||||||
|
return expect(result).toBe(genesetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GENE
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function addGeneToSet(genesetName: any, geneToAddToSet: any) {
|
||||||
|
const submitButton = `${genesetName}:submit-gene`;
|
||||||
|
|
||||||
|
await clickOn(`${genesetName}:add-new-gene-to-geneset`);
|
||||||
|
await typeInto("add-genes", geneToAddToSet);
|
||||||
|
await clickOn(submitButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function removeGene(geneSymbol: any) {
|
||||||
|
const targetId = `delete-from-geneset:${geneSymbol}`;
|
||||||
|
|
||||||
|
await clickOn(targetId);
|
||||||
|
|
||||||
|
await waitByClass("autosave-complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function assertGeneExistsInGeneset(geneSymbol: any) {
|
||||||
|
const handle = await waitByID(`${geneSymbol}:gene-label`);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
const result = await handle.evaluate((node) =>
|
||||||
|
node.getAttribute("aria-label")
|
||||||
|
);
|
||||||
|
|
||||||
|
return expect(result).toBe(geneSymbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function assertGeneDoesNotExist(geneSymbol: any) {
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
|
const result = await isElementPresent(getTestId(`${geneSymbol}:gene-label`));
|
||||||
|
|
||||||
|
await expect(result).toBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function expandGene(geneSymbol: any) {
|
||||||
|
await clickOn(`maximize-${geneSymbol}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CATEGORY
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function duplicateCategory(categoryName: any) {
|
||||||
|
await clickOn("open-annotation-dialog");
|
||||||
|
|
||||||
|
await typeInto("new-category-name", categoryName);
|
||||||
|
|
||||||
|
const dropdownOptionClass = "duplicate-category-dropdown-option";
|
||||||
|
|
||||||
|
await clickOnUntil("duplicate-category-dropdown", async () => {
|
||||||
|
await expect(page).toMatchElement(getTestClass(dropdownOptionClass));
|
||||||
|
});
|
||||||
|
|
||||||
|
const option = await expect(page).toMatchElement(
|
||||||
|
getTestClass(dropdownOptionClass)
|
||||||
|
);
|
||||||
|
|
||||||
|
await option.click();
|
||||||
|
|
||||||
|
await clickOnUntil("submit-category", async () => {
|
||||||
|
await expect(page).toMatchElement(
|
||||||
|
getTestId(`${categoryName}:category-expand`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitByClass("autosave-complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function renameCategory(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
oldCategoryName: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
newCategoryName: any
|
||||||
|
) {
|
||||||
|
await clickOn(`${oldCategoryName}:see-actions`);
|
||||||
|
await clickOn(`${oldCategoryName}:edit-category-mode`);
|
||||||
|
await clearInputAndTypeInto(
|
||||||
|
`${oldCategoryName}:edit-category-name-text`,
|
||||||
|
newCategoryName
|
||||||
|
);
|
||||||
|
await clickOn(`${oldCategoryName}:submit-category-edit`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function deleteCategory(categoryName: any) {
|
||||||
|
const targetId = `${categoryName}:delete-category`;
|
||||||
|
|
||||||
|
await clickOnUntil(`${categoryName}:see-actions`, async () => {
|
||||||
|
await expect(page).toMatchElement(getTestId(targetId));
|
||||||
|
});
|
||||||
|
|
||||||
|
await clickOn(targetId);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||||
|
await assertCategoryDoesNotExist();
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function createLabel(categoryName: any, labelName: any) {
|
||||||
|
/**
|
||||||
|
* (thuang): This explicit wait is needed, since currently showing
|
||||||
|
* the modal again quickly after the previous action dismissing the
|
||||||
|
* modal will persist the input value from the previous action.
|
||||||
|
*
|
||||||
|
* To reproduce:
|
||||||
|
* 1. Click on the plus sign to show the modal to add a new label to the category
|
||||||
|
* 2. Type `123` in the input box
|
||||||
|
* 3. Hover over your mouse over the plus sign and double click to quickly dismiss and
|
||||||
|
* invoke the modal again
|
||||||
|
* 4. You will see `123` is persisted in the input box
|
||||||
|
* 5. Expected behavior is to get an empty input box
|
||||||
|
*/
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
await clickOn(`${categoryName}:see-actions`);
|
||||||
|
|
||||||
|
await clickOn(`${categoryName}:add-new-label-to-category`);
|
||||||
|
|
||||||
|
await typeInto(`${categoryName}:new-label-name`, labelName);
|
||||||
|
|
||||||
|
await clickOn(`${categoryName}:submit-label`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function deleteLabel(categoryName: any, labelName: any) {
|
||||||
|
await expandCategory(categoryName);
|
||||||
|
await clickOn(`${categoryName}:${labelName}:see-actions`);
|
||||||
|
await clickOn(`${categoryName}:${labelName}:delete-label`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function renameLabel(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
categoryName: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
oldLabelName: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
newLabelName: any
|
||||||
|
) {
|
||||||
|
await expandCategory(categoryName);
|
||||||
|
await clickOn(`${categoryName}:${oldLabelName}:see-actions`);
|
||||||
|
await clickOn(`${categoryName}:${oldLabelName}:edit-label`);
|
||||||
|
await clearInputAndTypeInto(
|
||||||
|
`${categoryName}:${oldLabelName}:edit-label-name`,
|
||||||
|
newLabelName
|
||||||
|
);
|
||||||
|
await clickOn(`${categoryName}:${oldLabelName}:submit-label-edit`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function addGeneToSearch(geneName: any) {
|
||||||
|
await typeInto("gene-search", geneName);
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
await page.waitForSelector(`[data-testid='histogram-${geneName}']`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function subset(coordinatesAsPercent: any) {
|
||||||
|
// In order to deselect the selection after the subset, make sure we have some clear part
|
||||||
|
// of the scatterplot we can click on
|
||||||
|
assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99);
|
||||||
|
const lassoSelection = await calcDragCoordinates(
|
||||||
|
"layout-graph",
|
||||||
|
coordinatesAsPercent
|
||||||
|
);
|
||||||
|
await drag("layout-graph", lassoSelection.start, lassoSelection.end, true);
|
||||||
|
await clickOn("subset-button");
|
||||||
|
const clearCoordinate = await calcCoordinate("layout-graph", 0.5, 0.99);
|
||||||
|
await clickOnCoordinate("layout-graph", clearCoordinate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function setSellSet(cellSet: any, cellSetNum: any) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const selections = cellSet.filter((sel: any) => sel.kind === "categorical");
|
||||||
|
|
||||||
|
for (const selection of selections) {
|
||||||
|
await selectCategory(selection.metadata, selection.values, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await getCellSetCount(cellSetNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function runDiffExp(cellSet1: any, cellSet2: any) {
|
||||||
|
await setSellSet(cellSet1, 1);
|
||||||
|
await setSellSet(cellSet2, 2);
|
||||||
|
await clickOn("diffexp-button");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function bulkAddGenes(geneNames: any) {
|
||||||
|
await clickOn("section-bulk-add");
|
||||||
|
await typeInto("input-bulk-add", geneNames.join(","));
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function assertCategoryDoesNotExist(categoryName: any) {
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
|
const result = await isElementPresent(
|
||||||
|
getTestId(`${categoryName}:category-label`)
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(result).toBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function login() {
|
||||||
|
await goToPage(appUrlBase);
|
||||||
|
|
||||||
|
await clickOn("log-in");
|
||||||
|
|
||||||
|
// (thuang): Auth0 form is unstable and unsafe for input until verified
|
||||||
|
await waitUntilFormFieldStable('[name="email"]');
|
||||||
|
|
||||||
|
await expect(page).toFillForm("form", {
|
||||||
|
email: TEST_EMAIL,
|
||||||
|
password: TEST_PASSWORD,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({ waitUntil: "networkidle0" }),
|
||||||
|
expect(page).toClick('[name="submit"]'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(page.url()).toContain(appUrlBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function logout() {
|
||||||
|
await clickOnUntil("user-info", async () => {
|
||||||
|
await waitByID("log-out");
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({ waitUntil: "networkidle0" }),
|
||||||
|
clickOn("log-out"),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitByID("log-in");
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function waitUntilFormFieldStable(selector: any) {
|
||||||
|
const MAX_RETRY = 10;
|
||||||
|
const WAIT_FOR_MS = 200;
|
||||||
|
|
||||||
|
const EXPECTED_VALUE = "aaa";
|
||||||
|
|
||||||
|
let retry = 0;
|
||||||
|
|
||||||
|
while (retry < MAX_RETRY) {
|
||||||
|
try {
|
||||||
|
await expect(page).toFill(selector, EXPECTED_VALUE);
|
||||||
|
|
||||||
|
const fieldHandle = await expect(page).toMatchElement(selector);
|
||||||
|
|
||||||
|
const fieldValue = await page.evaluate(
|
||||||
|
(input) => input.value,
|
||||||
|
fieldHandle
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fieldValue).toBe(EXPECTED_VALUE);
|
||||||
|
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
retry += 1;
|
||||||
|
|
||||||
|
await page.waitForTimeout(WAIT_FOR_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retry === MAX_RETRY) {
|
||||||
|
throw Error("clickOnUntil() assertion failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|
||||||
@@ -58,10 +58,12 @@ describe("metadata loads", () => {
|
|||||||
const categories = await getAllCategoriesAndCounts(label);
|
const categories = await getAllCategoriesAndCounts(label);
|
||||||
|
|
||||||
expect(Object.keys(categories)).toMatchObject(
|
expect(Object.keys(categories)).toMatchObject(
|
||||||
|
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||||
Object.keys(data.categorical[label])
|
Object.keys(data.categorical[label])
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Object.values(categories)).toMatchObject(
|
expect(Object.values(categories)).toMatchObject(
|
||||||
|
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||||
Object.values(data.categorical[label])
|
Object.values(data.categorical[label])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -159,10 +161,12 @@ describe("subset", () => {
|
|||||||
const categories = await getAllCategoriesAndCounts(label);
|
const categories = await getAllCategoriesAndCounts(label);
|
||||||
|
|
||||||
expect(Object.keys(categories)).toMatchObject(
|
expect(Object.keys(categories)).toMatchObject(
|
||||||
|
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||||
Object.keys(data.subset.categorical[label])
|
Object.keys(data.subset.categorical[label])
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(Object.values(categories)).toMatchObject(
|
expect(Object.values(categories)).toMatchObject(
|
||||||
|
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||||
Object.values(data.subset.categorical[label])
|
Object.values(data.subset.categorical[label])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -195,6 +199,7 @@ describe("clipping", () => {
|
|||||||
test("clip continuous", async () => {
|
test("clip continuous", async () => {
|
||||||
await goToPage(appUrlBase);
|
await goToPage(appUrlBase);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message
|
||||||
await clip(data.clip.min, data.clip.max);
|
await clip(data.clip.min, data.clip.max);
|
||||||
const histBrushableAreaId = `histogram-${data.clip.metadata}-plot-brushable-area`;
|
const histBrushableAreaId = `histogram-${data.clip.metadata}-plot-brushable-area`;
|
||||||
const coords = await calcDragCoordinates(
|
const coords = await calcDragCoordinates(
|
||||||
@@ -254,6 +259,7 @@ describe("centroid labels", () => {
|
|||||||
const generatedLabels = await getAllByClass("centroid-label");
|
const generatedLabels = await getAllByClass("centroid-label");
|
||||||
// Number of labels generated should be equal to size of the object
|
// Number of labels generated should be equal to size of the object
|
||||||
expect(generatedLabels).toHaveLength(
|
expect(generatedLabels).toHaveLength(
|
||||||
|
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||||
Object.keys(data.categorical[label]).length
|
Object.keys(data.categorical[label]).length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -275,6 +281,7 @@ describe("graph overlay", () => {
|
|||||||
data.pan["coordinates-as-percent"]
|
data.pan["coordinates-as-percent"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||||
const categoryValue = Object.keys(data.categorical[category])[0];
|
const categoryValue = Object.keys(data.categorical[category])[0];
|
||||||
const initialCoordinates = await getElementCoordinates(
|
const initialCoordinates = await getElementCoordinates(
|
||||||
`${categoryValue}-centroid-label`
|
`${categoryValue}-centroid-label`
|
||||||
+74
-18
@@ -12,6 +12,7 @@ import {
|
|||||||
getTestId,
|
getTestId,
|
||||||
getTestClass,
|
getTestClass,
|
||||||
getAllByClass,
|
getAllByClass,
|
||||||
|
clickOnUntil,
|
||||||
getOneElementInnerHTML,
|
getOneElementInnerHTML,
|
||||||
} from "./puppeteerUtils";
|
} from "./puppeteerUtils";
|
||||||
|
|
||||||
@@ -76,7 +77,13 @@ const brushThisGeneGeneset = "brush_this_gene";
|
|||||||
const geneBrushedCellCount = "109";
|
const geneBrushedCellCount = "109";
|
||||||
const subsetGeneBrushedCellCount = "96";
|
const subsetGeneBrushedCellCount = "96";
|
||||||
|
|
||||||
async function setup(config) {
|
const genesetDescriptionID =
|
||||||
|
"geneset-description-tooltip-fourth_gene_set: fourth description";
|
||||||
|
const genesetDescriptionString = "fourth_gene_set: fourth description";
|
||||||
|
const genesetToCheckForDescription = "fourth_gene_set";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function setup(config: any) {
|
||||||
await goToPage(appUrlBase);
|
await goToPage(appUrlBase);
|
||||||
|
|
||||||
if (config.categoricalAnno) {
|
if (config.categoricalAnno) {
|
||||||
@@ -150,7 +157,8 @@ describe.each([
|
|||||||
await expect(page).toClick(getTestClass("pop-1-geneset-expand"));
|
await expect(page).toClick(getTestClass("pop-1-geneset-expand"));
|
||||||
|
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
(selector) => !document.querySelector(selector),
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(selector: any) => !document.querySelector(selector),
|
||||||
{},
|
{},
|
||||||
getTestClass("gene-loading-spinner")
|
getTestClass("gene-loading-spinner")
|
||||||
);
|
);
|
||||||
@@ -165,7 +173,8 @@ describe.each([
|
|||||||
await expect(page).toClick(getTestClass("pop-2-geneset-expand"));
|
await expect(page).toClick(getTestClass("pop-2-geneset-expand"));
|
||||||
|
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
(selector) => !document.querySelector(selector),
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(selector: any) => !document.querySelector(selector),
|
||||||
{},
|
{},
|
||||||
getTestClass("gene-loading-spinner")
|
getTestClass("gene-loading-spinner")
|
||||||
);
|
);
|
||||||
@@ -174,7 +183,7 @@ describe.each([
|
|||||||
|
|
||||||
expect(genesHTML).toMatchSnapshot();
|
expect(genesHTML).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
test("create a new geneset", async () => {
|
test("create a new geneset and undo/redo", async () => {
|
||||||
if (config.withSubset) return;
|
if (config.withSubset) return;
|
||||||
|
|
||||||
await setup(config);
|
await setup(config);
|
||||||
@@ -184,19 +193,45 @@ describe.each([
|
|||||||
await createGeneset(genesetName);
|
await createGeneset(genesetName);
|
||||||
/* note: as of June 2021, the aria label is in the truncate component which clones the element */
|
/* note: as of June 2021, the aria label is in the truncate component which clones the element */
|
||||||
await assertGenesetExists(genesetName);
|
await assertGenesetExists(genesetName);
|
||||||
|
await clickOn("undo");
|
||||||
|
await assertGenesetDoesNotExist(genesetName);
|
||||||
|
await clickOn("redo");
|
||||||
|
await assertGenesetExists(genesetName);
|
||||||
});
|
});
|
||||||
test("edit geneset name", async () => {
|
test("edit geneset name and undo/redo", async () => {
|
||||||
await setup(config);
|
await setup(config);
|
||||||
|
|
||||||
await editGenesetName(editableGenesetName, editText);
|
await editGenesetName(editableGenesetName, editText);
|
||||||
await assertGenesetExists(newGenesetName);
|
await assertGenesetExists(newGenesetName);
|
||||||
|
await clickOn("undo");
|
||||||
|
await assertGenesetExists(editableGenesetName);
|
||||||
|
await clickOn("redo");
|
||||||
|
await assertGenesetExists(newGenesetName);
|
||||||
});
|
});
|
||||||
test("delete a geneset", async () => {
|
test("delete a geneset and undo/redo", async () => {
|
||||||
if (config.withSubset) return;
|
if (config.withSubset) return;
|
||||||
|
|
||||||
await setup(config);
|
await setup(config);
|
||||||
|
|
||||||
await deleteGeneset(genesetToDeleteName);
|
await deleteGeneset(genesetToDeleteName);
|
||||||
|
await clickOn("undo");
|
||||||
|
await assertGenesetExists(genesetToDeleteName);
|
||||||
|
await clickOn("redo");
|
||||||
|
await assertGenesetDoesNotExist(genesetToDeleteName);
|
||||||
|
});
|
||||||
|
test("geneset description", async () => {
|
||||||
|
if (config.withSubset) return;
|
||||||
|
|
||||||
|
await setup(config);
|
||||||
|
|
||||||
|
await clickOnUntil(
|
||||||
|
`${genesetToCheckForDescription}:geneset-expand`,
|
||||||
|
async () => {
|
||||||
|
expect(page).toMatchElement(getTestId(genesetDescriptionID), {
|
||||||
|
text: genesetDescriptionString,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,12 +239,16 @@ describe.each([
|
|||||||
{ withSubset: true, tag: "subset" },
|
{ withSubset: true, tag: "subset" },
|
||||||
{ withSubset: false, tag: "whole" },
|
{ withSubset: false, tag: "whole" },
|
||||||
])("GENE crud operations and interactions", (config) => {
|
])("GENE crud operations and interactions", (config) => {
|
||||||
test("add a gene to geneset", async () => {
|
test("add a gene to geneset and undo/redo", async () => {
|
||||||
await setup(config);
|
await setup(config);
|
||||||
|
|
||||||
await addGeneToSet(setToAddGeneTo, geneToAddToSet);
|
await addGeneToSet(setToAddGeneTo, geneToAddToSet);
|
||||||
await expandGeneset(setToAddGeneTo);
|
await expandGeneset(setToAddGeneTo);
|
||||||
await assertGeneExistsInGeneset(geneToAddToSet);
|
await assertGeneExistsInGeneset(geneToAddToSet);
|
||||||
|
await clickOn("undo");
|
||||||
|
await assertGeneDoesNotExist(geneToAddToSet);
|
||||||
|
await clickOn("redo");
|
||||||
|
await assertGeneExistsInGeneset(geneToAddToSet);
|
||||||
});
|
});
|
||||||
test("expand gene and brush", async () => {
|
test("expand gene and brush", async () => {
|
||||||
await setup(config);
|
await setup(config);
|
||||||
@@ -240,7 +279,7 @@ describe.each([
|
|||||||
await colorByGene(geneToBrushAndColorBy);
|
await colorByGene(geneToBrushAndColorBy);
|
||||||
await assertColorLegendLabel(geneToBrushAndColorBy);
|
await assertColorLegendLabel(geneToBrushAndColorBy);
|
||||||
});
|
});
|
||||||
test("delete gene from geneset", async () => {
|
test("delete gene from geneset and undo/redo", async () => {
|
||||||
// We've already deleted the gene
|
// We've already deleted the gene
|
||||||
if (config.withSubset) return;
|
if (config.withSubset) return;
|
||||||
|
|
||||||
@@ -249,6 +288,10 @@ describe.each([
|
|||||||
await expandGeneset(setToRemoveFrom);
|
await expandGeneset(setToRemoveFrom);
|
||||||
await removeGene(geneToRemove);
|
await removeGene(geneToRemove);
|
||||||
await assertGeneDoesNotExist(geneToRemove);
|
await assertGeneDoesNotExist(geneToRemove);
|
||||||
|
await clickOn("undo");
|
||||||
|
await assertGeneExistsInGeneset(geneToRemove);
|
||||||
|
await clickOn("redo");
|
||||||
|
await assertGeneDoesNotExist(geneToRemove);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -362,8 +405,13 @@ describe.each([
|
|||||||
expect(actualLabelName).toBe(expectedLabelName);
|
expect(actualLabelName).toBe(expectedLabelName);
|
||||||
expect(actualLabelCount).toBe(expectedLabelCount);
|
expect(actualLabelCount).toBe(expectedLabelCount);
|
||||||
|
|
||||||
async function getInnerText(element, className) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
return element.$eval(getTestClass(className), (node) => node?.innerText);
|
async function getInnerText(element: any, className: any) {
|
||||||
|
return element.$eval(
|
||||||
|
getTestClass(className),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(node: any) => node?.innerText
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -388,7 +436,9 @@ describe.each([
|
|||||||
`categorical-value-count-${perTestCategoryName}-${perTestLabelName}`
|
`categorical-value-count-${perTestCategoryName}-${perTestLabelName}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
expect(await result.evaluate((node) => node.innerText)).toBe(
|
expect(await result.evaluate((node) => node.innerText)).toBe(
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'boolean' cannot be used as an index type.
|
||||||
data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
|
data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -448,6 +498,7 @@ describe.each([
|
|||||||
await createLabel(perTestCategoryName, labelName);
|
await createLabel(perTestCategoryName, labelName);
|
||||||
await assertLabelExists(perTestCategoryName, labelName);
|
await assertLabelExists(perTestCategoryName, labelName);
|
||||||
await clickOn("undo");
|
await clickOn("undo");
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
await assertLabelDoesNotExist(perTestCategoryName);
|
await assertLabelDoesNotExist(perTestCategoryName);
|
||||||
await clickOn("redo");
|
await clickOn("redo");
|
||||||
await assertLabelExists(perTestCategoryName, labelName);
|
await assertLabelExists(perTestCategoryName, labelName);
|
||||||
@@ -457,10 +508,12 @@ describe.each([
|
|||||||
await setup(config);
|
await setup(config);
|
||||||
|
|
||||||
await deleteLabel(perTestCategoryName, perTestLabelName);
|
await deleteLabel(perTestCategoryName, perTestLabelName);
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
await assertLabelDoesNotExist(perTestCategoryName);
|
await assertLabelDoesNotExist(perTestCategoryName);
|
||||||
await clickOn("undo");
|
await clickOn("undo");
|
||||||
await assertLabelExists(perTestCategoryName, perTestLabelName);
|
await assertLabelExists(perTestCategoryName, perTestLabelName);
|
||||||
await clickOn("redo");
|
await clickOn("redo");
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
await assertLabelDoesNotExist(perTestCategoryName);
|
await assertLabelDoesNotExist(perTestCategoryName);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -491,11 +544,9 @@ describe.each([
|
|||||||
const labels = await getAllByClass("categorical-row");
|
const labels = await getAllByClass("categorical-row");
|
||||||
|
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
labels.map((label) => {
|
labels.map((label) =>
|
||||||
return page.evaluate((element) => {
|
page.evaluate((element) => element.outerHTML, label)
|
||||||
return element.outerHTML;
|
)
|
||||||
}, label);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
@@ -523,9 +574,11 @@ describe.each([
|
|||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function assertCategoryExists(categoryName) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function assertCategoryExists(categoryName: any) {
|
||||||
const handle = await waitByID(`${categoryName}:category-label`);
|
const handle = await waitByID(`${categoryName}:category-label`);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
const result = await handle.evaluate((node) =>
|
const result = await handle.evaluate((node) =>
|
||||||
node.getAttribute("aria-label")
|
node.getAttribute("aria-label")
|
||||||
);
|
);
|
||||||
@@ -533,7 +586,8 @@ describe.each([
|
|||||||
return expect(result).toBe(categoryName);
|
return expect(result).toBe(categoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assertLabelExists(categoryName, labelName) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function assertLabelExists(categoryName: any, labelName: any) {
|
||||||
await expect(page).toMatchElement(
|
await expect(page).toMatchElement(
|
||||||
getTestId(`${categoryName}:category-expand`)
|
getTestId(`${categoryName}:category-expand`)
|
||||||
);
|
);
|
||||||
@@ -545,11 +599,13 @@ describe.each([
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
await previous.evaluate((node) => node.getAttribute("aria-label"))
|
await previous.evaluate((node) => node.getAttribute("aria-label"))
|
||||||
).toBe(labelName);
|
).toBe(labelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function assertLabelDoesNotExist(categoryName, labelName) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function assertLabelDoesNotExist(categoryName: any, labelName: any) {
|
||||||
await expandCategory(categoryName);
|
await expandCategory(categoryName);
|
||||||
const result = await page.$(
|
const result = await page.$(
|
||||||
`[data-testid='categorical-value-${categoryName}-${labelName}']`
|
`[data-testid='categorical-value-${categoryName}-${labelName}']`
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"testRunner": "jest-circus/runner",
|
"testRunner": "jest-circus/runner",
|
||||||
"preset": "jest-puppeteer",
|
"preset": "jest-puppeteer",
|
||||||
"testMatch": ["**/__tests__/**/?(*.)(spec|test).js?(x)"],
|
"testMatch": ["**/__tests__/**/?(*.)(spec|test).ts?(x)"],
|
||||||
"setupFiles": ["../setupMissingGlobals.js"],
|
"setupFiles": ["../setupMissingGlobals.ts"],
|
||||||
"setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.js"],
|
"setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.ts"],
|
||||||
"globalSetup": "../globalSetup.js",
|
"globalSetup": "../globalSetup.ts",
|
||||||
"globalTeardown": "jest-environment-puppeteer/teardown",
|
"globalTeardown": "jest-environment-puppeteer/teardown",
|
||||||
"testEnvironment": "./screenshot_env.js"
|
"testEnvironment": "./screenshot_env.js"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ beforeEach(async () => {
|
|||||||
const userAgent = await browser.userAgent();
|
const userAgent = await browser.userAgent();
|
||||||
await page.setUserAgent(`${userAgent}bot`);
|
await page.setUserAgent(`${userAgent}bot`);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2341) FIXME: Property '_client' is private and only accessible ... Remove this comment to see the full error message
|
||||||
await page._client.send("Animation.setPlaybackRate", { playbackRate: 12 });
|
await page._client.send("Animation.setPlaybackRate", { playbackRate: 12 });
|
||||||
|
|
||||||
page.on("pageerror", (err) => {
|
page.on("pageerror", (err) => {
|
||||||
@@ -49,7 +50,8 @@ beforeEach(async () => {
|
|||||||
}
|
}
|
||||||
const errorMsgText = await Promise.all(
|
const errorMsgText = await Promise.all(
|
||||||
// TODO can we do this without internal properties?
|
// TODO can we do this without internal properties?
|
||||||
msg.args().map((arg) => arg._remoteObject.description)
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
msg.args().map((arg: any) => arg._remoteObject.description)
|
||||||
);
|
);
|
||||||
throw new Error(`Console error: ${errorMsgText}`);
|
throw new Error(`Console error: ${errorMsgText}`);
|
||||||
}
|
}
|
||||||
@@ -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 */
|
|
||||||
@@ -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 */
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const PuppeteerEnvironment = require("jest-environment-puppeteer");
|
const PuppeteerEnvironment = require("jest-environment-puppeteer");
|
||||||
require("jest-circus");
|
require("jest-circus");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const ENV_DEFAULT = require("../../../environment.default.json");
|
const ENV_DEFAULT = require("../../../environment.default.json");
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2451) FIXME: Cannot redeclare block-scoped variable 'takeScreen... Remove this comment to see the full error message
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const takeScreenshot = require("./takeScreenshot");
|
const takeScreenshot = require("./takeScreenshot");
|
||||||
|
|
||||||
class ScreenshotEnvironment extends PuppeteerEnvironment {
|
class ScreenshotEnvironment extends PuppeteerEnvironment {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
// @ts-ignore FIXME: 'globalSetup.ts' cannot be compiled under '--isola... Remove this comment to see the full error message
|
||||||
const {
|
const {
|
||||||
SecretsManagerClient,
|
SecretsManagerClient,
|
||||||
GetSecretValueCommand,
|
GetSecretValueCommand,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
} = require("@aws-sdk/client-secrets-manager");
|
} = require("@aws-sdk/client-secrets-manager");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const { setup } = require("jest-environment-puppeteer");
|
const { setup } = require("jest-environment-puppeteer");
|
||||||
|
|
||||||
const client = new SecretsManagerClient({ region: "us-west-2" });
|
const client = new SecretsManagerClient({ region: "us-west-2" });
|
||||||
+20
-2
@@ -20,7 +20,16 @@ describe("cascade", () => {
|
|||||||
const reducer = cascadeReducers([
|
const reducer = cascadeReducers([
|
||||||
[
|
[
|
||||||
"foo",
|
"foo",
|
||||||
(currentState, action, nextSharedState, prevSharedState) => {
|
(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
currentState: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
action: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
nextSharedState: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
prevSharedState: any
|
||||||
|
) => {
|
||||||
expect(currentState).toBeUndefined();
|
expect(currentState).toBeUndefined();
|
||||||
expect(action).toEqual(topLevelAction);
|
expect(action).toEqual(topLevelAction);
|
||||||
expect(nextSharedState).toStrictEqual({});
|
expect(nextSharedState).toStrictEqual({});
|
||||||
@@ -30,7 +39,16 @@ describe("cascade", () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
"bar",
|
"bar",
|
||||||
(currentState, action, nextSharedState, prevSharedState) => {
|
(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
currentState: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
action: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
nextSharedState: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
prevSharedState: any
|
||||||
|
) => {
|
||||||
expect(currentState).toBeUndefined();
|
expect(currentState).toBeUndefined();
|
||||||
expect(action).toEqual(topLevelAction);
|
expect(action).toEqual(topLevelAction);
|
||||||
expect(nextSharedState).toStrictEqual({ foo: 0 });
|
expect(nextSharedState).toStrictEqual({ foo: 0 });
|
||||||
+2
@@ -501,6 +501,7 @@ describe("geneset: set tid", () => {
|
|||||||
test("not a number error", () => {
|
test("not a number error", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
genesetsReducer(
|
genesetsReducer(
|
||||||
|
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number' is not assignable to type 'undefined... Remove this comment to see the full error message
|
||||||
{ lastTid: 1 },
|
{ lastTid: 1 },
|
||||||
{
|
{
|
||||||
type: "geneset: set tid",
|
type: "geneset: set tid",
|
||||||
@@ -513,6 +514,7 @@ describe("geneset: set tid", () => {
|
|||||||
test("decrement error", () => {
|
test("decrement error", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
genesetsReducer(
|
genesetsReducer(
|
||||||
|
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number' is not assignable to type 'undefined... Remove this comment to see the full error message
|
||||||
{ lastTid: 1 },
|
{ lastTid: 1 },
|
||||||
{
|
{
|
||||||
type: "geneset: set tid",
|
type: "geneset: set tid",
|
||||||
+7
-7
@@ -2,6 +2,7 @@ import undoable from "../../src/reducers/undoable";
|
|||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
test("no keys", () => {
|
test("no keys", () => {
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2-3 arguments, but got 1.
|
||||||
expect(() => undoable(() => {})).toThrow();
|
expect(() => undoable(() => {})).toThrow();
|
||||||
expect(() => undoable(() => {}, null)).toThrow();
|
expect(() => undoable(() => {}, null)).toThrow();
|
||||||
expect(() => undoable(() => {}, [])).toThrow();
|
expect(() => undoable(() => {}, [])).toThrow();
|
||||||
@@ -23,9 +24,8 @@ describe("create", () => {
|
|||||||
describe("undo", () => {
|
describe("undo", () => {
|
||||||
test("expected state modifications", () => {
|
test("expected state modifications", () => {
|
||||||
const initialState = { a: 0, b: 1000 };
|
const initialState = { a: 0, b: 1000 };
|
||||||
const reducer = (state) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
return { a: state.a + 1, b: state.b + 1 };
|
const reducer = (state: any) => ({ a: state.a + 1, b: state.b + 1 });
|
||||||
};
|
|
||||||
const undoableReducer = undoable(reducer, ["a"]);
|
const undoableReducer = undoable(reducer, ["a"]);
|
||||||
|
|
||||||
const s1 = undoableReducer(initialState, { type: "test" });
|
const s1 = undoableReducer(initialState, { type: "test" });
|
||||||
@@ -43,10 +43,10 @@ describe("undo", () => {
|
|||||||
|
|
||||||
describe("redo", () => {
|
describe("redo", () => {
|
||||||
const initialState = { a: 0, b: 1000 };
|
const initialState = { a: 0, b: 1000 };
|
||||||
const reducer = (state) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
return { a: state.a + 1, b: state.b + 1 };
|
const reducer = (state: any) => ({ a: state.a + 1, b: state.b + 1 });
|
||||||
};
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
let UR;
|
let UR: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UR = undoable(reducer, ["a"]);
|
UR = undoable(reducer, ["a"]);
|
||||||
@@ -5,5 +5,6 @@ the jest test environment).
|
|||||||
|
|
||||||
import { TextDecoder, TextEncoder } from "util";
|
import { TextDecoder, TextEncoder } from "util";
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2322) FIXME: Type 'typeof TextDecoder' is not assignable to typ... Remove this comment to see the full error message
|
||||||
global.TextDecoder = TextDecoder;
|
global.TextDecoder = TextDecoder;
|
||||||
global.TextEncoder = TextEncoder;
|
global.TextEncoder = TextEncoder;
|
||||||
+39
-17
@@ -14,10 +14,13 @@ import { Dataframe } from "../../../src/util/dataframe";
|
|||||||
enableFetchMocks();
|
enableFetchMocks();
|
||||||
|
|
||||||
describe("AnnoMatrix", () => {
|
describe("AnnoMatrix", () => {
|
||||||
let annoMatrix;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let annoMatrix: any;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
fetch.resetMocks(); // reset all fetch mocking state
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).resetMocks(); // reset all fetch mocking state
|
||||||
|
// reset all fetch mocking state
|
||||||
annoMatrix = new AnnoMatrixLoader(
|
annoMatrix = new AnnoMatrixLoader(
|
||||||
serverMocks.baseDataURL,
|
serverMocks.baseDataURL,
|
||||||
serverMocks.schema.schema
|
serverMocks.schema.schema
|
||||||
@@ -36,7 +39,8 @@ describe("AnnoMatrix", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("simple single column fetch", async () => {
|
test("simple single column fetch", async () => {
|
||||||
fetch.once(serverMocks.annotationsObs(["name_0"]));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(serverMocks.annotationsObs(["name_0"]));
|
||||||
|
|
||||||
const df = await annoMatrix.fetch("obs", "name_0");
|
const df = await annoMatrix.fetch("obs", "name_0");
|
||||||
expect(df).toBeInstanceOf(Dataframe);
|
expect(df).toBeInstanceOf(Dataframe);
|
||||||
@@ -45,7 +49,8 @@ describe("AnnoMatrix", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("simple multi column fetch", async () => {
|
test("simple multi column fetch", async () => {
|
||||||
fetch
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any)
|
||||||
.once(serverMocks.annotationsObs(["name_0"]))
|
.once(serverMocks.annotationsObs(["name_0"]))
|
||||||
.once(serverMocks.annotationsObs(["n_genes"]));
|
.once(serverMocks.annotationsObs(["n_genes"]));
|
||||||
|
|
||||||
@@ -55,9 +60,13 @@ describe("AnnoMatrix", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("fetch from field", () => {
|
describe("fetch from field", () => {
|
||||||
const getLastTwo = async (field) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const getLastTwo = async (field: any) => {
|
||||||
const columnNames = annoMatrix.getMatrixColumns(field).slice(-2);
|
const columnNames = annoMatrix.getMatrixColumns(field).slice(-2);
|
||||||
fetch.mockResponses(...columnNames.map(() => serverMocks.responder));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockResponses(
|
||||||
|
...columnNames.map(() => serverMocks.responder)
|
||||||
|
);
|
||||||
await expect(
|
await expect(
|
||||||
annoMatrix.fetch(field, columnNames)
|
annoMatrix.fetch(field, columnNames)
|
||||||
).resolves.toBeInstanceOf(Dataframe);
|
).resolves.toBeInstanceOf(Dataframe);
|
||||||
@@ -70,19 +79,22 @@ describe("AnnoMatrix", () => {
|
|||||||
|
|
||||||
test("fetch - test all query forms", async () => {
|
test("fetch - test all query forms", async () => {
|
||||||
// single string is a column name
|
// single string is a column name
|
||||||
fetch.once(serverMocks.annotationsObs(["n_genes"]));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(serverMocks.annotationsObs(["n_genes"]));
|
||||||
await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf(
|
await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf(
|
||||||
Dataframe
|
Dataframe
|
||||||
);
|
);
|
||||||
|
|
||||||
// array of column names, expecting n_genes to be cached.
|
// array of column names, expecting n_genes to be cached.
|
||||||
fetch.once(serverMocks.annotationsObs(["percent_mito"]));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(serverMocks.annotationsObs(["percent_mito"]));
|
||||||
await expect(
|
await expect(
|
||||||
annoMatrix.fetch("obs", ["n_genes", "percent_mito"])
|
annoMatrix.fetch("obs", ["n_genes", "percent_mito"])
|
||||||
).resolves.toBeInstanceOf(Dataframe);
|
).resolves.toBeInstanceOf(Dataframe);
|
||||||
|
|
||||||
// more complex value filter query, enumerated
|
// more complex value filter query, enumerated
|
||||||
fetch.once(serverMocks.responder);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(serverMocks.responder);
|
||||||
await expect(
|
await expect(
|
||||||
annoMatrix.fetch("X", {
|
annoMatrix.fetch("X", {
|
||||||
where: {
|
where: {
|
||||||
@@ -95,7 +107,8 @@ describe("AnnoMatrix", () => {
|
|||||||
|
|
||||||
// more complex value filter query, range
|
// more complex value filter query, range
|
||||||
const varIndex = annoMatrix.schema.annotations.var.index;
|
const varIndex = annoMatrix.schema.annotations.var.index;
|
||||||
fetch
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any)
|
||||||
.once(
|
.once(
|
||||||
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]])
|
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]])
|
||||||
)
|
)
|
||||||
@@ -171,7 +184,8 @@ describe("AnnoMatrix", () => {
|
|||||||
expect(am1.nObs).toEqual(am2.nObs);
|
expect(am1.nObs).toEqual(am2.nObs);
|
||||||
expect(am1.nVar).toEqual(am2.nVar);
|
expect(am1.nVar).toEqual(am2.nVar);
|
||||||
|
|
||||||
fetch
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any)
|
||||||
.once(serverMocks.annotationsObs(["n_genes"]))
|
.once(serverMocks.annotationsObs(["n_genes"]))
|
||||||
.once(serverMocks.annotationsObs(["n_genes"]));
|
.once(serverMocks.annotationsObs(["n_genes"]));
|
||||||
const ng1 = await am1.fetch("obs", "n_genes");
|
const ng1 = await am1.fetch("obs", "n_genes");
|
||||||
@@ -185,9 +199,11 @@ describe("AnnoMatrix", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("add/drop column", () => {
|
describe("add/drop column", () => {
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
|
||||||
async function addDrop(base) {
|
async function addDrop(base) {
|
||||||
expect(base.getMatrixColumns("obs")).not.toContain("foo");
|
expect(base.getMatrixColumns("obs")).not.toContain("foo");
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
await expect(base.fetch("obs", "foo")).rejects.toThrow(
|
await expect(base.fetch("obs", "foo")).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
);
|
);
|
||||||
@@ -212,7 +228,8 @@ describe("AnnoMatrix", () => {
|
|||||||
const am2 = am1.dropObsColumn("foo");
|
const am2 = am1.dropObsColumn("foo");
|
||||||
expect(base.getMatrixColumns("obs")).not.toContain("foo");
|
expect(base.getMatrixColumns("obs")).not.toContain("foo");
|
||||||
expect(am2.getMatrixColumns("obs")).not.toContain("foo");
|
expect(am2.getMatrixColumns("obs")).not.toContain("foo");
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
await expect(am2.fetch("obs", "foo")).rejects.toThrow(
|
await expect(am2.fetch("obs", "foo")).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
);
|
);
|
||||||
@@ -235,14 +252,16 @@ describe("AnnoMatrix", () => {
|
|||||||
const am4 = clip(am3, 0, 1);
|
const am4 = clip(am3, 0, 1);
|
||||||
await addDrop(am4);
|
await addDrop(am4);
|
||||||
|
|
||||||
fetch.mockResponse(serverMocks.responder);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockResponse(serverMocks.responder);
|
||||||
|
|
||||||
await am1.fetch("obs", am1.getMatrixColumns("obs"));
|
await am1.fetch("obs", am1.getMatrixColumns("obs"));
|
||||||
await am2.fetch("obs", am2.getMatrixColumns("obs"));
|
await am2.fetch("obs", am2.getMatrixColumns("obs"));
|
||||||
await am3.fetch("obs", am3.getMatrixColumns("obs"));
|
await am3.fetch("obs", am3.getMatrixColumns("obs"));
|
||||||
await am4.fetch("obs", am4.getMatrixColumns("obs"));
|
await am4.fetch("obs", am4.getMatrixColumns("obs"));
|
||||||
|
|
||||||
fetch.resetMocks();
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).resetMocks();
|
||||||
|
|
||||||
await addDrop(am1);
|
await addDrop(am1);
|
||||||
await addDrop(am2);
|
await addDrop(am2);
|
||||||
@@ -252,6 +271,7 @@ describe("AnnoMatrix", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("setObsColumnValues", () => {
|
describe("setObsColumnValues", () => {
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
|
||||||
async function addSetDrop(base) {
|
async function addSetDrop(base) {
|
||||||
/* add column */
|
/* add column */
|
||||||
let am = base.addObsColumn(
|
let am = base.addObsColumn(
|
||||||
@@ -287,7 +307,8 @@ describe("AnnoMatrix", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/* drop column */
|
/* drop column */
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
am = am1.dropObsColumn("test");
|
am = am1.dropObsColumn("test");
|
||||||
await expect(am.fetch("obs", "test")).rejects.toThrow(
|
await expect(am.fetch("obs", "test")).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
@@ -308,7 +329,8 @@ describe("AnnoMatrix", () => {
|
|||||||
const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]);
|
const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]);
|
||||||
await addSetDrop(am3);
|
await addSetDrop(am3);
|
||||||
|
|
||||||
fetch.mockResponse(serverMocks.responder);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockResponse(serverMocks.responder);
|
||||||
|
|
||||||
await am1.fetch("obs", am1.getMatrixColumns("obs"));
|
await am1.fetch("obs", am1.getMatrixColumns("obs"));
|
||||||
await am2.fetch("obs", am2.getMatrixColumns("obs"));
|
await am2.fetch("obs", am2.getMatrixColumns("obs"));
|
||||||
+95
-44
@@ -17,11 +17,15 @@ import { rangeFill } from "../../../src/util/range";
|
|||||||
enableFetchMocks();
|
enableFetchMocks();
|
||||||
|
|
||||||
describe("AnnoMatrixCrossfilter", () => {
|
describe("AnnoMatrixCrossfilter", () => {
|
||||||
let annoMatrix;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
let crossfilter;
|
let annoMatrix: any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let crossfilter: any;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
fetch.resetMocks(); // reset all fetch mocking state
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).resetMocks(); // reset all fetch mocking state
|
||||||
|
// reset all fetch mocking state
|
||||||
annoMatrix = new AnnoMatrixLoader(
|
annoMatrix = new AnnoMatrixLoader(
|
||||||
serverMocks.baseDataURL,
|
serverMocks.baseDataURL,
|
||||||
serverMocks.schema.schema
|
serverMocks.schema.schema
|
||||||
@@ -67,7 +71,10 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
crossfilter.obsCrossfilter.hasDimension("obs/louvain")
|
crossfilter.obsCrossfilter.hasDimension("obs/louvain")
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
|
|
||||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(
|
||||||
|
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
|
||||||
|
);
|
||||||
let newCrossfilter = await crossfilter.select("obs", "louvain", {
|
let newCrossfilter = await crossfilter.select("obs", "louvain", {
|
||||||
mode: "none",
|
mode: "none",
|
||||||
});
|
});
|
||||||
@@ -76,7 +83,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(
|
expect(
|
||||||
newCrossfilter.obsCrossfilter.hasDimension("obs/louvain")
|
newCrossfilter.obsCrossfilter.hasDimension("obs/louvain")
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(fetch.mock.calls).toHaveLength(1);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
expect((fetch as any).mock.calls).toHaveLength(1);
|
||||||
|
|
||||||
newCrossfilter = await crossfilter.select("obs", "louvain", {
|
newCrossfilter = await crossfilter.select("obs", "louvain", {
|
||||||
mode: "all",
|
mode: "all",
|
||||||
@@ -87,7 +95,10 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
test("simple column select", async () => {
|
test("simple column select", async () => {
|
||||||
let xfltr;
|
let xfltr;
|
||||||
|
|
||||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(
|
||||||
|
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
|
||||||
|
);
|
||||||
xfltr = await crossfilter.select("obs", "louvain", {
|
xfltr = await crossfilter.select("obs", "louvain", {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: ["NK cells", "B cells"],
|
values: ["NK cells", "B cells"],
|
||||||
@@ -105,6 +116,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(xfltr.allSelectedLabels()).toEqual(
|
expect(xfltr.allSelectedLabels()).toEqual(
|
||||||
Int32Array.from(
|
Int32Array.from(
|
||||||
obsLouvain.reduce((acc, val, idx) => {
|
obsLouvain.reduce((acc, val, idx) => {
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message
|
||||||
if (val === "NK cells" || val === "B cells") acc.push(idx);
|
if (val === "NK cells" || val === "B cells") acc.push(idx);
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
@@ -124,10 +136,13 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
const values = df.col("louvain").asArray();
|
const values = df.col("louvain").asArray();
|
||||||
const selected = xfltr.allSelectedMask();
|
const selected = xfltr.allSelectedMask();
|
||||||
values.every(
|
values.every(
|
||||||
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(val: any, idx: any) =>
|
||||||
|
!["NK cells", "B cells"].includes(val) !== !selected[idx]
|
||||||
);
|
);
|
||||||
|
|
||||||
fetch.once(
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(
|
||||||
serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)])
|
serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)])
|
||||||
);
|
);
|
||||||
xfltr = await xfltr.select("obs", "n_genes", {
|
xfltr = await xfltr.select("obs", "n_genes", {
|
||||||
@@ -146,6 +161,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
val < 500 &&
|
val < 500 &&
|
||||||
(louvain === "NK cells" || louvain === "B cells")
|
(louvain === "NK cells" || louvain === "B cells")
|
||||||
)
|
)
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message
|
||||||
acc.push(idx);
|
acc.push(idx);
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
@@ -160,7 +176,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
const varIndex = annoMatrix.schema.annotations.var.index;
|
const varIndex = annoMatrix.schema.annotations.var.index;
|
||||||
|
|
||||||
const { nObs } = annoMatrix.schema.dataframe;
|
const { nObs } = annoMatrix.schema.dataframe;
|
||||||
fetch.once(
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(
|
||||||
serverMocks.dataframeResponse(
|
serverMocks.dataframeResponse(
|
||||||
["TEST"],
|
["TEST"],
|
||||||
[rangeFill(new Float32Array(nObs), 0, 0.1)]
|
[rangeFill(new Float32Array(nObs), 0, 0.1)]
|
||||||
@@ -196,14 +213,19 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
const values = df.icol(0).asArray();
|
const values = df.icol(0).asArray();
|
||||||
const selected = xfltr.allSelectedMask();
|
const selected = xfltr.allSelectedMask();
|
||||||
values.every((val, idx) => !(val >= 0 && val <= 50) !== !selected[idx]);
|
values.every(
|
||||||
expect(selected.reduce((acc, val) => (val ? acc + 1 : acc), 0)).toEqual(
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
xfltr.countSelected()
|
(val: any, idx: any) => !(val >= 0 && val <= 50) !== !selected[idx]
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
selected.reduce((acc: any, val: any) => (val ? acc + 1 : acc), 0)
|
||||||
|
).toEqual(xfltr.countSelected());
|
||||||
});
|
});
|
||||||
|
|
||||||
test("spatial column select", async () => {
|
test("spatial column select", async () => {
|
||||||
fetch.once(
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(
|
||||||
serverMocks.dataframeResponse(
|
serverMocks.dataframeResponse(
|
||||||
["umap_0", "umap_1"],
|
["umap_0", "umap_1"],
|
||||||
[Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])]
|
[Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])]
|
||||||
@@ -222,6 +244,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
test("select on subset", async () => {
|
test("select on subset", async () => {
|
||||||
const mask = new Uint8Array(annoMatrix.nObs).fill(0);
|
const mask = new Uint8Array(annoMatrix.nObs).fill(0);
|
||||||
for (let i = 0; i < mask.length; i += 2) {
|
for (let i = 0; i < mask.length; i += 2) {
|
||||||
|
// @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'number'.
|
||||||
mask[i] = true;
|
mask[i] = true;
|
||||||
}
|
}
|
||||||
const annoMatrixSubset = isubsetMask(annoMatrix, mask);
|
const annoMatrixSubset = isubsetMask(annoMatrix, mask);
|
||||||
@@ -230,7 +253,10 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset);
|
let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset);
|
||||||
expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs);
|
expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs);
|
||||||
|
|
||||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(
|
||||||
|
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
|
||||||
|
);
|
||||||
xfltr = await xfltr.select("obs", "louvain", {
|
xfltr = await xfltr.select("obs", "louvain", {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: ["NK cells", "B cells"],
|
values: ["NK cells", "B cells"],
|
||||||
@@ -243,7 +269,9 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
const values = df.col("louvain").asArray();
|
const values = df.col("louvain").asArray();
|
||||||
const selected = xfltr.allSelectedMask();
|
const selected = xfltr.allSelectedMask();
|
||||||
values.every(
|
values.every(
|
||||||
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(val: any, idx: any) =>
|
||||||
|
!["NK cells", "B cells"].includes(val) !== !selected[idx]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -256,7 +284,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
"unable to obsSelect upon the var dimension"
|
"unable to obsSelect upon the var dimension"
|
||||||
);
|
);
|
||||||
|
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
await expect(crossfilter.select("obs", "foo")).rejects.toThrow(
|
await expect(crossfilter.select("obs", "foo")).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
);
|
);
|
||||||
@@ -267,24 +296,29 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
/*
|
/*
|
||||||
test the matrix mutators via crossfilter proxy
|
test the matrix mutators via crossfilter proxy
|
||||||
*/
|
*/
|
||||||
async function helperAddTestCol(cf, colName, colSchema = null) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function helperAddTestCol(cf: any, colName: any, colSchema = null) {
|
||||||
expect(
|
expect(
|
||||||
cf.annoMatrix.getMatrixColumns("obs").includes(colName)
|
cf.annoMatrix.getMatrixColumns("obs").includes(colName)
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
|
|
||||||
if (colSchema === null) {
|
if (colSchema === null) {
|
||||||
|
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ name: any; type: string; categories: strin... Remove this comment to see the full error message
|
||||||
colSchema = {
|
colSchema = {
|
||||||
name: colName,
|
name: colName,
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
categories: ["toasty"],
|
categories: ["toasty"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
colSchema.name = colName;
|
colSchema.name = colName;
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
const initValue = colSchema.categories[0];
|
const initValue = colSchema.categories[0];
|
||||||
const xfltr = cf.addObsColumn(colSchema, Array, initValue);
|
const xfltr = cf.addObsColumn(colSchema, Array, initValue);
|
||||||
expect(
|
expect(
|
||||||
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
||||||
(v) => v.name === colName
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(v: any) => v.name === colName
|
||||||
)
|
)
|
||||||
).toHaveLength(1);
|
).toHaveLength(1);
|
||||||
const df = await xfltr.annoMatrix.fetch("obs", colName);
|
const df = await xfltr.annoMatrix.fetch("obs", colName);
|
||||||
@@ -314,7 +348,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
||||||
(v) => v.name === "foo"
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(v: any) => v.name === "foo"
|
||||||
)
|
)
|
||||||
).toHaveLength(1);
|
).toHaveLength(1);
|
||||||
|
|
||||||
@@ -323,8 +358,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(
|
expect(
|
||||||
df
|
df
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.every((v) => v === "A")
|
.every((v: any) => v === "A")
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
// check that we catch dups
|
// check that we catch dups
|
||||||
@@ -361,11 +396,13 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
xfltr = xfltr.dropObsColumn("foo");
|
xfltr = xfltr.dropObsColumn("foo");
|
||||||
expect(
|
expect(
|
||||||
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
|
||||||
(v) => v.name === "foo"
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(v: any) => v.name === "foo"
|
||||||
)
|
)
|
||||||
).toHaveLength(0);
|
).toHaveLength(0);
|
||||||
expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toBeUndefined();
|
expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toBeUndefined();
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
|
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
);
|
);
|
||||||
@@ -378,7 +415,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
xfltr = xfltr.dropObsColumn("bar");
|
xfltr = xfltr.dropObsColumn("bar");
|
||||||
|
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
|
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
);
|
);
|
||||||
@@ -404,7 +442,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
type: "categorical",
|
type: "categorical",
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
|
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
);
|
);
|
||||||
@@ -419,7 +458,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
xfltr = xfltr.renameObsColumn("bar", "xyz");
|
xfltr = xfltr.renameObsColumn("bar", "xyz");
|
||||||
|
|
||||||
fetch.mockRejectOnce(new Error("unknown column name"));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).mockRejectOnce(new Error("unknown column name"));
|
||||||
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
|
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
|
||||||
"unknown column name"
|
"unknown column name"
|
||||||
);
|
);
|
||||||
@@ -429,7 +469,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("addObsAnnoCategory", async () => {
|
test("addObsAnnoCategory", async () => {
|
||||||
let xfltr;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let xfltr: any;
|
||||||
|
|
||||||
// catch unknown or readonly columns
|
// catch unknown or readonly columns
|
||||||
expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow(
|
expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow(
|
||||||
@@ -440,6 +481,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
).toThrow("Unknown or readonly obs column");
|
).toThrow("Unknown or readonly obs column");
|
||||||
|
|
||||||
// add a column and then add category to it
|
// add a column and then add category to it
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
|
||||||
xfltr = await helperAddTestCol(crossfilter, "foo", {
|
xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||||
name: "foo",
|
name: "foo",
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
@@ -458,6 +500,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// now same, but ensure we have built an index before doing the operation
|
// now same, but ensure we have built an index before doing the operation
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
|
||||||
xfltr = await helperAddTestCol(crossfilter, "bar", {
|
xfltr = await helperAddTestCol(crossfilter, "bar", {
|
||||||
name: "bar",
|
name: "bar",
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
@@ -486,6 +529,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
crossfilter.removeObsAnnoCategory("undefined-name", "mumble")
|
crossfilter.removeObsAnnoCategory("undefined-name", "mumble")
|
||||||
).rejects.toThrow("Unknown or readonly obs column");
|
).rejects.toThrow("Unknown or readonly obs column");
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
|
||||||
xfltr = await helperAddTestCol(crossfilter, "foo", {
|
xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||||
name: "foo",
|
name: "foo",
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
@@ -495,8 +539,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(
|
expect(
|
||||||
(await xfltr.annoMatrix.fetch("obs", "foo"))
|
(await xfltr.annoMatrix.fetch("obs", "foo"))
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.every((v) => v === "unassigned")
|
.every((v: any) => v === "unassigned")
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||||
name: "foo",
|
name: "foo",
|
||||||
@@ -514,8 +558,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(
|
expect(
|
||||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.every((v) => v === "unassigned")
|
.every((v: any) => v === "unassigned")
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||||
name: "foo",
|
name: "foo",
|
||||||
@@ -537,8 +581,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(
|
expect(
|
||||||
(await xfltr2.annoMatrix.fetch("obs", "foo"))
|
(await xfltr2.annoMatrix.fetch("obs", "foo"))
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.every((v) => v === "red")
|
.every((v: any) => v === "red")
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||||
name: "foo",
|
name: "foo",
|
||||||
@@ -556,6 +600,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
crossfilter.setObsColumnValues("undefined-name", [0], "mumble")
|
crossfilter.setObsColumnValues("undefined-name", [0], "mumble")
|
||||||
).rejects.toThrow("Unknown or readonly obs column");
|
).rejects.toThrow("Unknown or readonly obs column");
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
|
||||||
let xfltr = await helperAddTestCol(crossfilter, "foo", {
|
let xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||||
name: "foo",
|
name: "foo",
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
@@ -572,8 +617,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(
|
expect(
|
||||||
(await xfltr.annoMatrix.fetch("obs", "foo"))
|
(await xfltr.annoMatrix.fetch("obs", "foo"))
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.every((v) => v === "unassigned")
|
.every((v: any) => v === "unassigned")
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
|
const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
|
||||||
expect(
|
expect(
|
||||||
@@ -581,7 +626,8 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray()
|
||||||
.every(
|
.every(
|
||||||
(v, i) =>
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(v: any, i: any) =>
|
||||||
v === "unassigned" || (v === "purple" && (i === 0 || i === 10))
|
v === "unassigned" || (v === "purple" && (i === 0 || i === 10))
|
||||||
)
|
)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
@@ -615,6 +661,7 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
crossfilter.resetObsColumnValues("undefined-name", "red", "blue")
|
crossfilter.resetObsColumnValues("undefined-name", "red", "blue")
|
||||||
).rejects.toThrow("Unknown or readonly obs column");
|
).rejects.toThrow("Unknown or readonly obs column");
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
|
||||||
let xfltr = await helperAddTestCol(crossfilter, "foo", {
|
let xfltr = await helperAddTestCol(crossfilter, "foo", {
|
||||||
name: "foo",
|
name: "foo",
|
||||||
type: "categorical",
|
type: "categorical",
|
||||||
@@ -638,22 +685,22 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
expect(
|
expect(
|
||||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.filter((v) => v === "purple")
|
.filter((v: any) => v === "purple")
|
||||||
).toHaveLength(2);
|
).toHaveLength(2);
|
||||||
|
|
||||||
xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta");
|
xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta");
|
||||||
expect(
|
expect(
|
||||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.filter((v) => v === "magenta")
|
.filter((v: any) => v === "magenta")
|
||||||
).toHaveLength(2);
|
).toHaveLength(2);
|
||||||
expect(
|
expect(
|
||||||
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
(await xfltr1.annoMatrix.fetch("obs", "foo"))
|
||||||
.col("foo")
|
.col("foo")
|
||||||
.asArray()
|
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
.filter((v) => v === "purple")
|
.filter((v: any) => v === "purple")
|
||||||
).toHaveLength(0);
|
).toHaveLength(0);
|
||||||
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
|
||||||
name: "foo",
|
name: "foo",
|
||||||
@@ -673,12 +720,16 @@ describe("AnnoMatrixCrossfilter", () => {
|
|||||||
describe("edge cases", () => {
|
describe("edge cases", () => {
|
||||||
test("transition from empty annoMatrix", async () => {
|
test("transition from empty annoMatrix", async () => {
|
||||||
// select before fetch needs to work
|
// select before fetch needs to work
|
||||||
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(fetch as any).once(
|
||||||
|
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
|
||||||
|
);
|
||||||
const xfltr = await crossfilter.select("obs", "louvain", {
|
const xfltr = await crossfilter.select("obs", "louvain", {
|
||||||
mode: "exact",
|
mode: "exact",
|
||||||
values: "B cells",
|
values: "B cells",
|
||||||
});
|
});
|
||||||
expect(fetch.mock.calls).toHaveLength(1);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
expect((fetch as any).mock.calls).toHaveLength(1);
|
||||||
expect(xfltr.obsCrossfilter.hasDimension("obs/louvain")).toBeTruthy();
|
expect(xfltr.obsCrossfilter.hasDimension("obs/louvain")).toBeTruthy();
|
||||||
expect(xfltr.obsCrossfilter.all()).toBe(xfltr.annoMatrix._cache.obs);
|
expect(xfltr.obsCrossfilter.all()).toBe(xfltr.annoMatrix._cache.obs);
|
||||||
expect(xfltr.countSelected()).toEqual(
|
expect(xfltr.countSelected()).toEqual(
|
||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
export const baseDataURL = "https://a.fake.url/api/v0.2";
|
export const baseDataURL = "https://a.fake.url/api/v0.2";
|
||||||
|
|
||||||
window.CELLXGENE = {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(window as any).CELLXGENE = {
|
||||||
API: {
|
API: {
|
||||||
prefix: baseDataURL,
|
prefix: baseDataURL,
|
||||||
version: "v0.2/",
|
version: "v0.2/",
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
import { schema } from "./schema";
|
|
||||||
import { Dataframe, KeyIndex } from "../../../../src/util/dataframe";
|
|
||||||
import { encodeMatrixFBS } from "../../../../src/util/stateManager/matrix";
|
|
||||||
|
|
||||||
const indexedSchema = {
|
|
||||||
obsByName: Object.fromEntries(
|
|
||||||
schema.schema.annotations.obs.columns.map((v) => [v.name, v]) ?? []
|
|
||||||
),
|
|
||||||
varByName: Object.fromEntries(
|
|
||||||
schema.schema.annotations.var.columns.map((v) => [v.name, v]) ?? []
|
|
||||||
),
|
|
||||||
embByName: Object.fromEntries(
|
|
||||||
schema.schema.layout.obs.map((v) => [v.name, v]) ?? []
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
function makeMockColumn(s, length) {
|
|
||||||
const { type } = s;
|
|
||||||
switch (type) {
|
|
||||||
case "int32":
|
|
||||||
return new Int32Array(length).fill(Math.floor(99 * Math.random()));
|
|
||||||
|
|
||||||
case "string":
|
|
||||||
return new Array(length).fill("test");
|
|
||||||
|
|
||||||
case "float32":
|
|
||||||
return new Float32Array(length).fill(99 * Math.random());
|
|
||||||
|
|
||||||
case "boolean":
|
|
||||||
return new Array(length).fill(false);
|
|
||||||
|
|
||||||
case "categorical":
|
|
||||||
return new Array(length).fill(s.categories[0]);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error("unkonwn type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEncodedDataframe(colNames, length, colSchemas) {
|
|
||||||
const colIndex = new KeyIndex(colNames);
|
|
||||||
const columns = colSchemas.map((s) => makeMockColumn(s, length));
|
|
||||||
const df = new Dataframe([length, colNames.length], columns, null, colIndex);
|
|
||||||
const body = encodeMatrixFBS(df);
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dataframeResponse(colNames, columns) {
|
|
||||||
const colIndex = new KeyIndex(colNames);
|
|
||||||
const df = new Dataframe(
|
|
||||||
[columns[0].length, colNames.length],
|
|
||||||
columns,
|
|
||||||
null,
|
|
||||||
colIndex
|
|
||||||
);
|
|
||||||
const body = encodeMatrixFBS(df);
|
|
||||||
const headers = new Headers({
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
});
|
|
||||||
return () => Promise.resolve({ body, init: { status: 200, headers } });
|
|
||||||
}
|
|
||||||
|
|
||||||
function annotationObsResponse(request) {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const params = Array.from(url.searchParams.entries());
|
|
||||||
const names = params
|
|
||||||
.filter(([k]) => k === "annotation-name")
|
|
||||||
.map(([, v]) => v);
|
|
||||||
if (!names.every((n) => indexedSchema.obsByName[n])) {
|
|
||||||
return Promise.reject(new Error("bad obs annotation name in URL"));
|
|
||||||
}
|
|
||||||
const colSchemas = names.map((n) => indexedSchema.obsByName[n]);
|
|
||||||
const body = getEncodedDataframe(
|
|
||||||
names,
|
|
||||||
schema.schema.dataframe.nObs,
|
|
||||||
colSchemas
|
|
||||||
);
|
|
||||||
|
|
||||||
const headers = new Headers({
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
});
|
|
||||||
return Promise.resolve({
|
|
||||||
body,
|
|
||||||
init: { status: 200, headers },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function annotationVarResponse(request) {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const params = Array.from(url.searchParams.entries());
|
|
||||||
const names = params
|
|
||||||
.filter(([k]) => k === "annotation-name")
|
|
||||||
.map(([, v]) => v);
|
|
||||||
if (!names.every((n) => indexedSchema.varByName[n])) {
|
|
||||||
return Promise.reject(new Error("bad var annotation name in URL"));
|
|
||||||
}
|
|
||||||
const colSchemas = names.map((n) => indexedSchema.varByName[n]);
|
|
||||||
const body = getEncodedDataframe(
|
|
||||||
names,
|
|
||||||
schema.schema.dataframe.nVar,
|
|
||||||
colSchemas
|
|
||||||
);
|
|
||||||
|
|
||||||
const headers = new Headers({
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
});
|
|
||||||
return Promise.resolve({
|
|
||||||
body,
|
|
||||||
init: { status: 200, headers },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function layoutObsResponse(request) {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const params = Array.from(url.searchParams.entries());
|
|
||||||
const names = params.filter(([k]) => k === "layout-name").map(([, v]) => v);
|
|
||||||
if (!names.every((n) => indexedSchema.embByName[n])) {
|
|
||||||
return Promise.reject(new Error("bad layout name in URL"));
|
|
||||||
}
|
|
||||||
const dims = names.map((n) => indexedSchema.embByName[n].dims).flat();
|
|
||||||
const colSchemas = names
|
|
||||||
.map((n) => [indexedSchema.embByName[n], indexedSchema.embByName[n]])
|
|
||||||
.flat();
|
|
||||||
const body = getEncodedDataframe(
|
|
||||||
dims,
|
|
||||||
schema.schema.dataframe.nObs,
|
|
||||||
colSchemas
|
|
||||||
);
|
|
||||||
|
|
||||||
const headers = new Headers({
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
});
|
|
||||||
return Promise.resolve({
|
|
||||||
body,
|
|
||||||
init: { status: 200, headers },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function dataVarResponse(request) {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const params = Array.from(url.searchParams.entries());
|
|
||||||
|
|
||||||
const colNames = params.map((v) => `${v[0]}/${v[1]}`);
|
|
||||||
const colSchemas = colNames.map(() => schema.schema.dataframe);
|
|
||||||
const body = getEncodedDataframe(
|
|
||||||
colNames,
|
|
||||||
schema.schema.dataframe.nObs,
|
|
||||||
colSchemas
|
|
||||||
);
|
|
||||||
|
|
||||||
const headers = new Headers({
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
});
|
|
||||||
return Promise.resolve({
|
|
||||||
body,
|
|
||||||
init: { status: 200, headers },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function responder(request) {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
const { pathname } = url;
|
|
||||||
if (pathname.endsWith("/annotations/obs")) {
|
|
||||||
return annotationObsResponse(request);
|
|
||||||
}
|
|
||||||
if (pathname.endsWith("/annotations/var")) {
|
|
||||||
return annotationVarResponse(request);
|
|
||||||
}
|
|
||||||
if (pathname.endsWith("/layout/obs")) {
|
|
||||||
return layoutObsResponse(request);
|
|
||||||
}
|
|
||||||
if (pathname.endsWith("/data/var")) {
|
|
||||||
return dataVarResponse(request);
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error("bad URL"));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function withExpected(expectedURL, expectedParams) {
|
|
||||||
/*
|
|
||||||
Do some additional error checking
|
|
||||||
*/
|
|
||||||
return (request) => {
|
|
||||||
// if URL is bogus, reject the promise
|
|
||||||
const url = new URL(request.url);
|
|
||||||
if (!url.pathname.endsWith(expectedURL)) {
|
|
||||||
return Promise.reject(new Error("Unexpected URL!"));
|
|
||||||
}
|
|
||||||
const params = Array.from(url.searchParams.entries()).sort(
|
|
||||||
(a, b) => a[0] < b[0]
|
|
||||||
);
|
|
||||||
expectedParams = expectedParams.slice().sort((a, b) => a[0] < b[0]);
|
|
||||||
|
|
||||||
if (
|
|
||||||
params.length !== expectedParams.length ||
|
|
||||||
!params.every(
|
|
||||||
(p, i) => p[0] === expectedParams[i][0] && p[1] === expectedParams[i][1]
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return Promise.reject(new Error("unexpected name requested in URL"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return responder(request);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function annotationsObs(names) {
|
|
||||||
return withExpected(
|
|
||||||
"/annotations/obs",
|
|
||||||
names.map((name) => ["annotation-name", name])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
import { schema } from "./schema";
|
||||||
|
import { Dataframe, KeyIndex } from "../../../../src/util/dataframe";
|
||||||
|
import { encodeMatrixFBS } from "../../../../src/util/stateManager/matrix";
|
||||||
|
|
||||||
|
const indexedSchema = {
|
||||||
|
obsByName: Object.fromEntries(
|
||||||
|
schema.schema.annotations.obs.columns.map((v) => [v.name, v]) ?? []
|
||||||
|
),
|
||||||
|
varByName: Object.fromEntries(
|
||||||
|
schema.schema.annotations.var.columns.map((v) => [v.name, v]) ?? []
|
||||||
|
),
|
||||||
|
embByName: Object.fromEntries(
|
||||||
|
schema.schema.layout.obs.map((v) => [v.name, v]) ?? []
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function makeMockColumn(s: any, length: any) {
|
||||||
|
const { type } = s;
|
||||||
|
switch (type) {
|
||||||
|
case "int32":
|
||||||
|
return new Int32Array(length).fill(Math.floor(99 * Math.random()));
|
||||||
|
|
||||||
|
case "string":
|
||||||
|
return new Array(length).fill("test");
|
||||||
|
|
||||||
|
case "float32":
|
||||||
|
return new Float32Array(length).fill(99 * Math.random());
|
||||||
|
|
||||||
|
case "boolean":
|
||||||
|
return new Array(length).fill(false);
|
||||||
|
|
||||||
|
case "categorical":
|
||||||
|
return new Array(length).fill(s.categories[0]);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("unkonwn type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function getEncodedDataframe(colNames: any, length: any, colSchemas: any) {
|
||||||
|
const colIndex = new KeyIndex(colNames);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const columns = colSchemas.map((s: any) => makeMockColumn(s, length));
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
|
const df = new Dataframe([length, colNames.length], columns, null, colIndex);
|
||||||
|
const body = encodeMatrixFBS(df);
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export function dataframeResponse(colNames: any, columns: any) {
|
||||||
|
const colIndex = new KeyIndex(colNames);
|
||||||
|
const df = new Dataframe(
|
||||||
|
[columns[0].length, colNames.length],
|
||||||
|
columns,
|
||||||
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
|
colIndex
|
||||||
|
);
|
||||||
|
const body = encodeMatrixFBS(df);
|
||||||
|
const headers = new Headers({
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
});
|
||||||
|
return () => Promise.resolve({ body, init: { status: 200, headers } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function annotationObsResponse(request: any) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const params = Array.from((url.searchParams as any).entries());
|
||||||
|
const names = params
|
||||||
|
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||||
|
.filter(([k]) => k === "annotation-name")
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '([, v]: [any, any]) => any' is n... Remove this comment to see the full error message
|
||||||
|
.map(([, v]) => v);
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
if (!names.every((n) => indexedSchema.obsByName[n])) {
|
||||||
|
return Promise.reject(new Error("bad obs annotation name in URL"));
|
||||||
|
}
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
const colSchemas = names.map((n) => indexedSchema.obsByName[n]);
|
||||||
|
const body = getEncodedDataframe(
|
||||||
|
names,
|
||||||
|
schema.schema.dataframe.nObs,
|
||||||
|
colSchemas
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = new Headers({
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
});
|
||||||
|
return Promise.resolve({
|
||||||
|
body,
|
||||||
|
init: { status: 200, headers },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function annotationVarResponse(request: any) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const params = Array.from((url.searchParams as any).entries());
|
||||||
|
const names = params
|
||||||
|
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||||
|
.filter(([k]) => k === "annotation-name")
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '([, v]: [any, any]) => any' is n... Remove this comment to see the full error message
|
||||||
|
.map(([, v]) => v);
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
if (!names.every((n) => indexedSchema.varByName[n])) {
|
||||||
|
return Promise.reject(new Error("bad var annotation name in URL"));
|
||||||
|
}
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
const colSchemas = names.map((n) => indexedSchema.varByName[n]);
|
||||||
|
const body = getEncodedDataframe(
|
||||||
|
names,
|
||||||
|
schema.schema.dataframe.nVar,
|
||||||
|
colSchemas
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = new Headers({
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
});
|
||||||
|
return Promise.resolve({
|
||||||
|
body,
|
||||||
|
init: { status: 200, headers },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function layoutObsResponse(request: any) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const params = Array.from((url.searchParams as any).entries());
|
||||||
|
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||||
|
const names = params.filter(([k]) => k === "layout-name").map(([, v]) => v);
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
if (!names.every((n) => indexedSchema.embByName[n])) {
|
||||||
|
return Promise.reject(new Error("bad layout name in URL"));
|
||||||
|
}
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
const dims = names.map((n) => indexedSchema.embByName[n].dims).flat();
|
||||||
|
const colSchemas = names
|
||||||
|
// @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.
|
||||||
|
.map((n) => [indexedSchema.embByName[n], indexedSchema.embByName[n]])
|
||||||
|
.flat();
|
||||||
|
const body = getEncodedDataframe(
|
||||||
|
dims,
|
||||||
|
schema.schema.dataframe.nObs,
|
||||||
|
colSchemas
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = new Headers({
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
});
|
||||||
|
return Promise.resolve({
|
||||||
|
body,
|
||||||
|
init: { status: 200, headers },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function dataVarResponse(request: any) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const params = Array.from((url.searchParams as any).entries());
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const colNames = params.map((v) => `${(v as any)[0]}/${(v as any)[1]}`);
|
||||||
|
const colSchemas = colNames.map(() => schema.schema.dataframe);
|
||||||
|
const body = getEncodedDataframe(
|
||||||
|
colNames,
|
||||||
|
schema.schema.dataframe.nObs,
|
||||||
|
colSchemas
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = new Headers({
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
});
|
||||||
|
return Promise.resolve({
|
||||||
|
body,
|
||||||
|
init: { status: 200, headers },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export function responder(request: any) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const { pathname } = url;
|
||||||
|
if (pathname.endsWith("/annotations/obs")) {
|
||||||
|
return annotationObsResponse(request);
|
||||||
|
}
|
||||||
|
if (pathname.endsWith("/annotations/var")) {
|
||||||
|
return annotationVarResponse(request);
|
||||||
|
}
|
||||||
|
if (pathname.endsWith("/layout/obs")) {
|
||||||
|
return layoutObsResponse(request);
|
||||||
|
}
|
||||||
|
if (pathname.endsWith("/data/var")) {
|
||||||
|
return dataVarResponse(request);
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error("bad URL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export function withExpected(expectedURL: any, expectedParams: any) {
|
||||||
|
/*
|
||||||
|
Do some additional error checking
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
return (request: any) => {
|
||||||
|
// if URL is bogus, reject the promise
|
||||||
|
const url = new URL(request.url);
|
||||||
|
if (!url.pathname.endsWith(expectedURL)) {
|
||||||
|
return Promise.reject(new Error("Unexpected URL!"));
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const params = Array.from((url.searchParams as any).entries()).sort(
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '(a: unknown, b: unknown) => bool... Remove this comment to see the full error message
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(a, b) => (a as any)[0] < (b as any)[0]
|
||||||
|
);
|
||||||
|
expectedParams = expectedParams
|
||||||
|
.slice() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
.sort((a: any, b: any) => a[0] < b[0]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
params.length !== expectedParams.length ||
|
||||||
|
!params.every(
|
||||||
|
(p, i) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(p as any)[0] === expectedParams[i][0] && // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(p as any)[1] === expectedParams[i][1]
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return Promise.reject(new Error("unexpected name requested in URL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return responder(request);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export function annotationsObs(names: any) {
|
||||||
|
return withExpected(
|
||||||
|
"/annotations/obs",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
names.map((name: any) => ["annotation-name", name])
|
||||||
|
);
|
||||||
|
}
|
||||||
+11
-3
@@ -218,10 +218,18 @@ describe("whereCache", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(wc.where.field.queryField.has("queryColumn")).toEqual(true);
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
expect(wc.where.field.queryField.get("queryColumn")).toBeInstanceOf(Map);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
expect((wc.where as any).field.queryField.has("queryColumn")).toEqual(true);
|
||||||
expect(
|
expect(
|
||||||
wc.where.field.queryField.get("queryColumn").has("queryValue")
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(wc.where as any).field.queryField.get("queryColumn")
|
||||||
|
).toBeInstanceOf(Map);
|
||||||
|
expect(
|
||||||
|
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(wc.where as any).field.queryField.get("queryColumn").has("queryValue")
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
|
expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
|
||||||
});
|
});
|
||||||
@@ -5,19 +5,22 @@ import quantile from "../../src/util/quantile";
|
|||||||
import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix";
|
import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix";
|
||||||
import * as REST from "./stateManager/sampleResponses";
|
import * as REST from "./stateManager/sampleResponses";
|
||||||
import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers";
|
import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers";
|
||||||
import { _normalizeCategoricalSchema } from "../../src/annoMatrix/schema";
|
import { normalizeWritableCategoricalSchema } from "../../src/annoMatrix/normalize";
|
||||||
|
|
||||||
describe("centroid", () => {
|
describe("centroid", () => {
|
||||||
let schema;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
let obsAnnotations;
|
let schema: any;
|
||||||
let obsLayout;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let obsAnnotations: any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let obsLayout: any;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
schema = indexEntireSchema(cloneDeep(REST.schema.schema));
|
schema = indexEntireSchema(cloneDeep(REST.schema.schema));
|
||||||
obsAnnotations = matrixFBSToDataframe(REST.annotationsObs);
|
obsAnnotations = matrixFBSToDataframe(REST.annotationsObs);
|
||||||
obsLayout = matrixFBSToDataframe(REST.layoutObs);
|
obsLayout = matrixFBSToDataframe(REST.layoutObs);
|
||||||
|
|
||||||
_normalizeCategoricalSchema(
|
normalizeWritableCategoricalSchema(
|
||||||
schema.annotations.obsByName.field3,
|
schema.annotations.obsByName.field3,
|
||||||
obsAnnotations.col("field3")
|
obsAnnotations.col("field3")
|
||||||
);
|
);
|
||||||
@@ -44,7 +47,8 @@ describe("centroid", () => {
|
|||||||
quantile([0.5], obsLayout.col("umap_1").asArray())[0],
|
quantile([0.5], obsLayout.col("umap_1").asArray())[0],
|
||||||
];
|
];
|
||||||
|
|
||||||
centroidResult.forEach((coordinate) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
centroidResult.forEach((coordinate: any) => {
|
||||||
expect(coordinate).toEqual(expectedResult);
|
expect(coordinate).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -68,7 +72,8 @@ describe("centroid", () => {
|
|||||||
quantile([0.5], obsLayout.col("umap_1").asArray())[0],
|
quantile([0.5], obsLayout.col("umap_1").asArray())[0],
|
||||||
];
|
];
|
||||||
|
|
||||||
centroidResult.forEach((coordinate) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
centroidResult.forEach((coordinate: any) => {
|
||||||
expect(coordinate).toEqual(expectedResult);
|
expect(coordinate).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
+43
-5
@@ -29,6 +29,7 @@ describe("dataframe constructor", () => {
|
|||||||
const df = new Dataframe.Dataframe(
|
const df = new Dataframe.Dataframe(
|
||||||
[3, 2],
|
[3, 2],
|
||||||
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
|
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
|
||||||
new Dataframe.DenseInt32Index([2, 1, 0]),
|
new Dataframe.DenseInt32Index([2, 1, 0]),
|
||||||
new Dataframe.KeyIndex(["A", "B"])
|
new Dataframe.KeyIndex(["A", "B"])
|
||||||
);
|
);
|
||||||
@@ -55,6 +56,7 @@ describe("simple data access", () => {
|
|||||||
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
|
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
|
||||||
["red", "blue", "green", "nan"],
|
["red", "blue", "green", "nan"],
|
||||||
],
|
],
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
|
||||||
new Dataframe.DenseInt32Index([3, 2, 1, 0]),
|
new Dataframe.DenseInt32Index([3, 2, 1, 0]),
|
||||||
new Dataframe.KeyIndex(["numbers", "colors"])
|
new Dataframe.KeyIndex(["numbers", "colors"])
|
||||||
);
|
);
|
||||||
@@ -139,10 +141,12 @@ describe("dataframe subsetting", () => {
|
|||||||
["red", "green", "blue"],
|
["red", "green", "blue"],
|
||||||
],
|
],
|
||||||
null, // identity index
|
null, // identity index
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
||||||
);
|
);
|
||||||
|
|
||||||
test("all rows, one column", () => {
|
test("all rows, one column", () => {
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
const dfA = sourceDf.subset(null, ["colors"]);
|
const dfA = sourceDf.subset(null, ["colors"]);
|
||||||
expect(dfA).toBeDefined();
|
expect(dfA).toBeDefined();
|
||||||
expect(dfA.dims).toEqual([3, 1]);
|
expect(dfA.dims).toEqual([3, 1]);
|
||||||
@@ -158,6 +162,7 @@ describe("dataframe subsetting", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("all rows, two columns", () => {
|
test("all rows, two columns", () => {
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
const dfB = sourceDf.subset(null, ["float32", "colors"]);
|
const dfB = sourceDf.subset(null, ["float32", "colors"]);
|
||||||
expect(dfB).toBeDefined();
|
expect(dfB).toBeDefined();
|
||||||
expect(dfB.dims).toEqual([3, 2]);
|
expect(dfB.dims).toEqual([3, 2]);
|
||||||
@@ -227,6 +232,7 @@ describe("dataframe subsetting", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("two rows, two colums", () => {
|
test("two rows, two colums", () => {
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
const dfF = sourceDf.subset([0, 2], ["int32", "float32"]);
|
const dfF = sourceDf.subset([0, 2], ["int32", "float32"]);
|
||||||
expect(dfF).toBeDefined();
|
expect(dfF).toBeDefined();
|
||||||
expect(dfF.dims).toEqual([2, 2]);
|
expect(dfF.dims).toEqual([2, 2]);
|
||||||
@@ -236,6 +242,7 @@ describe("dataframe subsetting", () => {
|
|||||||
expect(dfF.colIndex.labels()).toEqual(["int32", "float32"]);
|
expect(dfF.colIndex.labels()).toEqual(["int32", "float32"]);
|
||||||
|
|
||||||
// reverse the row and column order
|
// reverse the row and column order
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
const dfFr = sourceDf.subset([2, 0], ["float32", "int32"]);
|
const dfFr = sourceDf.subset([2, 0], ["float32", "int32"]);
|
||||||
expect(dfFr).toBeDefined();
|
expect(dfFr).toBeDefined();
|
||||||
expect(dfFr.dims).toEqual([2, 2]);
|
expect(dfFr.dims).toEqual([2, 2]);
|
||||||
@@ -248,6 +255,7 @@ describe("dataframe subsetting", () => {
|
|||||||
test("withRowIndex", () => {
|
test("withRowIndex", () => {
|
||||||
const df = sourceDf.subset(
|
const df = sourceDf.subset(
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
["int32", "float32"],
|
["int32", "float32"],
|
||||||
new Dataframe.DenseInt32Index([3, 2, 1])
|
new Dataframe.DenseInt32Index([3, 2, 1])
|
||||||
);
|
);
|
||||||
@@ -258,12 +266,15 @@ describe("dataframe subsetting", () => {
|
|||||||
|
|
||||||
test("withRowIndex error checks", () => {
|
test("withRowIndex error checks", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
sourceDf.subset(null, ["red"], new Dataframe.IdentityInt32Index(1))
|
sourceDf.subset(null, ["red"], new Dataframe.IdentityInt32Index(1))
|
||||||
).toThrow(RangeError);
|
).toThrow(RangeError);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
sourceDf.subset(null, ["red"], new Dataframe.DenseInt32Index([0, 1]))
|
sourceDf.subset(null, ["red"], new Dataframe.DenseInt32Index([0, 1]))
|
||||||
).toThrow(RangeError);
|
).toThrow(RangeError);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
sourceDf.subset(null, ["red"], new Dataframe.KeyIndex([0, 1, 2, 3]))
|
sourceDf.subset(null, ["red"], new Dataframe.KeyIndex([0, 1, 2, 3]))
|
||||||
).toThrow(RangeError);
|
).toThrow(RangeError);
|
||||||
});
|
});
|
||||||
@@ -278,12 +289,14 @@ describe("dataframe subsetting", () => {
|
|||||||
new Float32Array([4.4, 5.5, 6.6]),
|
new Float32Array([4.4, 5.5, 6.6]),
|
||||||
["red", "green", "blue"],
|
["red", "green", "blue"],
|
||||||
],
|
],
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
|
||||||
new Dataframe.DenseInt32Index([2, 4, 6]),
|
new Dataframe.DenseInt32Index([2, 4, 6]),
|
||||||
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
||||||
);
|
);
|
||||||
|
|
||||||
const dfA = sourceDf.isubsetMask(
|
const dfA = sourceDf.isubsetMask(
|
||||||
new Uint8Array([0, 1, 1]),
|
new Uint8Array([0, 1, 1]),
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'Uint8Array' is not assignable to... Remove this comment to see the full error message
|
||||||
new Uint8Array([1, 0, 0, 1])
|
new Uint8Array([1, 0, 0, 1])
|
||||||
);
|
);
|
||||||
expect(dfA.dims).toEqual([2, 2]);
|
expect(dfA.dims).toEqual([2, 2]);
|
||||||
@@ -303,6 +316,7 @@ describe("dataframe subsetting", () => {
|
|||||||
["red", "green", "blue"],
|
["red", "green", "blue"],
|
||||||
],
|
],
|
||||||
null, // identity index
|
null, // identity index
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -316,6 +330,7 @@ describe("dataframe subsetting", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("all rows, two cols", () => {
|
test("all rows, two cols", () => {
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
const dfA = sourceDf.isubset(null, [1, 2]);
|
const dfA = sourceDf.isubset(null, [1, 2]);
|
||||||
expect(dfA.dims).toEqual([3, 2]);
|
expect(dfA.dims).toEqual([3, 2]);
|
||||||
expect(dfA.icol(0).asArray()).toEqual(["A", "B", "C"]);
|
expect(dfA.icol(0).asArray()).toEqual(["A", "B", "C"]);
|
||||||
@@ -361,6 +376,7 @@ describe("dataframe factories", () => {
|
|||||||
const dfA = new Dataframe.Dataframe(
|
const dfA = new Dataframe.Dataframe(
|
||||||
[3, 2],
|
[3, 2],
|
||||||
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
|
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
|
||||||
new Dataframe.DenseInt32Index([2, 1, 0]),
|
new Dataframe.DenseInt32Index([2, 1, 0]),
|
||||||
new Dataframe.KeyIndex(["A", "B"])
|
new Dataframe.KeyIndex(["A", "B"])
|
||||||
);
|
);
|
||||||
@@ -385,6 +401,7 @@ describe("dataframe factories", () => {
|
|||||||
[true, false],
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors", "bools"])
|
new Dataframe.KeyIndex(["colors", "bools"])
|
||||||
);
|
);
|
||||||
const dfA = df.withCol("numbers", [1, 0]);
|
const dfA = df.withCol("numbers", [1, 0]);
|
||||||
@@ -408,6 +425,7 @@ describe("dataframe factories", () => {
|
|||||||
[true, false],
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
|
||||||
new Dataframe.DenseInt32Index([74, 75])
|
new Dataframe.DenseInt32Index([74, 75])
|
||||||
);
|
);
|
||||||
const dfA = df.withCol(72, [1, 0]);
|
const dfA = df.withCol(72, [1, 0]);
|
||||||
@@ -433,6 +451,7 @@ describe("dataframe factories", () => {
|
|||||||
[true, false],
|
[true, false],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
|
||||||
new Dataframe.DenseInt32Index([74, 75])
|
new Dataframe.DenseInt32Index([74, 75])
|
||||||
);
|
);
|
||||||
const dfA = df.withCol(999, [1, 0]);
|
const dfA = df.withCol(999, [1, 0]);
|
||||||
@@ -541,6 +560,7 @@ describe("dataframe factories", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -549,11 +569,14 @@ describe("dataframe factories", () => {
|
|||||||
[3, 1],
|
[3, 1],
|
||||||
[["red", "blue", "green"]],
|
[["red", "blue", "green"]],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colorsA"])
|
new Dataframe.KeyIndex(["colorsA"])
|
||||||
);
|
);
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
expect(() => dfA.withColsFrom(dfB)).toThrow(RangeError);
|
expect(() => dfA.withColsFrom(dfB)).toThrow(RangeError);
|
||||||
|
|
||||||
/* duplicate labels should throw an error */
|
/* duplicate labels should throw an error */
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
expect(() => dfA.withColsFrom(dfA)).toThrow(Error);
|
expect(() => dfA.withColsFrom(dfA)).toThrow(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -564,15 +587,18 @@ describe("dataframe factories", () => {
|
|||||||
[2, 1],
|
[2, 1],
|
||||||
[["red", "blue"]],
|
[["red", "blue"]],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors"])
|
new Dataframe.KeyIndex(["colors"])
|
||||||
);
|
);
|
||||||
const dfB = new Dataframe.Dataframe(
|
const dfB = new Dataframe.Dataframe(
|
||||||
[2, 1],
|
[2, 1],
|
||||||
[[true, false]],
|
[[true, false]],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["bools"])
|
new Dataframe.KeyIndex(["bools"])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
const dfLikeA = dfEmpty.withColsFrom(dfA);
|
const dfLikeA = dfEmpty.withColsFrom(dfA);
|
||||||
expect(dfLikeA).toBeDefined();
|
expect(dfLikeA).toBeDefined();
|
||||||
expect(dfLikeA.dims).toEqual(dfA.dims);
|
expect(dfLikeA.dims).toEqual(dfA.dims);
|
||||||
@@ -581,6 +607,7 @@ describe("dataframe factories", () => {
|
|||||||
expect(dfLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
|
expect(dfLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
|
||||||
expect(dfLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray());
|
expect(dfLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray());
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
const dfAlsoLikeA = dfA.withColsFrom(dfEmpty);
|
const dfAlsoLikeA = dfA.withColsFrom(dfEmpty);
|
||||||
expect(dfAlsoLikeA).toBeDefined();
|
expect(dfAlsoLikeA).toBeDefined();
|
||||||
expect(dfAlsoLikeA.dims).toEqual(dfA.dims);
|
expect(dfAlsoLikeA.dims).toEqual(dfA.dims);
|
||||||
@@ -589,6 +616,7 @@ describe("dataframe factories", () => {
|
|||||||
expect(dfAlsoLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
|
expect(dfAlsoLikeA.rowIndex.labels()).toEqual(dfA.rowIndex.labels());
|
||||||
expect(dfAlsoLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray());
|
expect(dfAlsoLikeA.icol(0).asArray()).toEqual(dfA.icol(0).asArray());
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
|
||||||
const dfC = dfA.withColsFrom(dfB);
|
const dfC = dfA.withColsFrom(dfB);
|
||||||
expect(dfC).toBeDefined();
|
expect(dfC).toBeDefined();
|
||||||
expect(dfC.dims).toEqual([2, 2]);
|
expect(dfC.dims).toEqual([2, 2]);
|
||||||
@@ -605,6 +633,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 1],
|
[2, 1],
|
||||||
[["red", "blue"]],
|
[["red", "blue"]],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors"])
|
new Dataframe.KeyIndex(["colors"])
|
||||||
);
|
);
|
||||||
const dfB = new Dataframe.Dataframe(
|
const dfB = new Dataframe.Dataframe(
|
||||||
@@ -615,6 +644,7 @@ describe("dataframe factories", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -647,6 +677,7 @@ describe("dataframe factories", () => {
|
|||||||
[2, 1],
|
[2, 1],
|
||||||
[["red", "blue"]],
|
[["red", "blue"]],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors"])
|
new Dataframe.KeyIndex(["colors"])
|
||||||
);
|
);
|
||||||
const dfB = new Dataframe.Dataframe(
|
const dfB = new Dataframe.Dataframe(
|
||||||
@@ -657,6 +688,7 @@ describe("dataframe factories", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -680,6 +712,7 @@ describe("dataframe factories", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
|
||||||
);
|
);
|
||||||
const dfA = df.dropCol("colors");
|
const dfA = df.dropCol("colors");
|
||||||
@@ -751,6 +784,7 @@ describe("dataframe factories", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'DenseInt32Index' is not assignab... Remove this comment to see the full error message
|
||||||
new Dataframe.DenseInt32Index([102, 101, 100])
|
new Dataframe.DenseInt32Index([102, 101, 100])
|
||||||
);
|
);
|
||||||
const dfA = df.dropCol(101);
|
const dfA = df.dropCol(101);
|
||||||
@@ -777,7 +811,8 @@ describe("dataframe factories", () => {
|
|||||||
new Float64Array(3).fill(1.1),
|
new Float64Array(3).fill(1.1),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
const dfB = dfA.mapColumns((col, idx) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const dfB = dfA.mapColumns((col: any, idx: any) => {
|
||||||
expect(dfA.icol(idx).asArray()).toBe(col);
|
expect(dfA.icol(idx).asArray()).toBe(col);
|
||||||
return col;
|
return col;
|
||||||
});
|
});
|
||||||
@@ -793,9 +828,7 @@ describe("dataframe factories", () => {
|
|||||||
[3, 3],
|
[3, 3],
|
||||||
[new Array(3).fill(0), new Array(3).fill(0), new Array(3).fill(0)]
|
[new Array(3).fill(0), new Array(3).fill(0), new Array(3).fill(0)]
|
||||||
);
|
);
|
||||||
const dfB = dfA.mapColumns(() => {
|
const dfB = dfA.mapColumns(() => new Array(3).fill(1));
|
||||||
return new Array(3).fill(1);
|
|
||||||
});
|
|
||||||
expect(dfA).not.toBe(dfB);
|
expect(dfA).not.toBe(dfB);
|
||||||
expect(dfB.iat(0, 0)).toEqual(1);
|
expect(dfB.iat(0, 0)).toEqual(1);
|
||||||
expect(dfB.iat(0, 1)).toEqual(1);
|
expect(dfB.iat(0, 1)).toEqual(1);
|
||||||
@@ -822,6 +855,7 @@ describe("dataframe factories", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["A", "B"])
|
new Dataframe.KeyIndex(["A", "B"])
|
||||||
);
|
);
|
||||||
const dfB = dfA.renameCol("B", "C");
|
const dfB = dfA.renameCol("B", "C");
|
||||||
@@ -834,7 +868,8 @@ describe("dataframe factories", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("dataframe col", () => {
|
describe("dataframe col", () => {
|
||||||
let df = null;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let df: any = null;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
df = new Dataframe.Dataframe(
|
df = new Dataframe.Dataframe(
|
||||||
[2, 2],
|
[2, 2],
|
||||||
@@ -843,6 +878,7 @@ describe("dataframe col", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["A", "B"])
|
new Dataframe.KeyIndex(["A", "B"])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1195,6 +1231,7 @@ describe("label indexing", () => {
|
|||||||
test("create", () => {
|
test("create", () => {
|
||||||
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
|
||||||
expect(() => new Dataframe.KeyIndex(["dup", "dup"])).toThrow(Error);
|
expect(() => new Dataframe.KeyIndex(["dup", "dup"])).toThrow(Error);
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||||
expect(new Dataframe.KeyIndex().size()).toEqual(0);
|
expect(new Dataframe.KeyIndex().size()).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1367,6 +1404,7 @@ describe("corner cases", () => {
|
|||||||
[1, 0],
|
[1, 0],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["A", "B"])
|
new Dataframe.KeyIndex(["A", "B"])
|
||||||
);
|
);
|
||||||
|
|
||||||
+4
@@ -6,6 +6,7 @@ describe("Dataframe column histogram", () => {
|
|||||||
[3, 3],
|
[3, 3],
|
||||||
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["name", "cat", "value"])
|
new Dataframe.KeyIndex(["name", "cat", "value"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ describe("Dataframe column histogram", () => {
|
|||||||
[3, 3],
|
[3, 3],
|
||||||
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["name", "cat", "value"])
|
new Dataframe.KeyIndex(["name", "cat", "value"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ describe("Dataframe column histogram", () => {
|
|||||||
[3, 3],
|
[3, 3],
|
||||||
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["name", "cat", "value"])
|
new Dataframe.KeyIndex(["name", "cat", "value"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -68,6 +71,7 @@ describe("Dataframe column histogram", () => {
|
|||||||
[3, 3],
|
[3, 3],
|
||||||
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
[["n1", "n2", "n3"], ["c1", "c2", "c3"], new Int32Array([0, 1, 2])],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["name", "cat", "value"])
|
new Dataframe.KeyIndex(["name", "cat", "value"])
|
||||||
);
|
);
|
||||||
|
|
||||||
+7
-1
@@ -1,6 +1,7 @@
|
|||||||
import * as Dataframe from "../../../src/util/dataframe";
|
import * as Dataframe from "../../../src/util/dataframe";
|
||||||
|
|
||||||
function float32Conversion(f) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function float32Conversion(f: any) {
|
||||||
return new Float32Array([f])[0];
|
return new Float32Array([f])[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ describe("Dataframe column summary", () => {
|
|||||||
[1],
|
[1],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex([
|
new Dataframe.KeyIndex([
|
||||||
"name",
|
"name",
|
||||||
"nameString",
|
"nameString",
|
||||||
@@ -106,6 +108,7 @@ describe("Dataframe column summary", () => {
|
|||||||
[1, false, "0"],
|
[1, false, "0"],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex([
|
new Dataframe.KeyIndex([
|
||||||
"name",
|
"name",
|
||||||
"nameString",
|
"nameString",
|
||||||
@@ -174,6 +177,7 @@ describe("Dataframe column summary", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining([1, false, "0"]),
|
categories: expect.arrayContaining([1, false, "0"]),
|
||||||
|
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||||
categoryCounts: new Map([
|
categoryCounts: new Map([
|
||||||
[1, 1],
|
[1, 1],
|
||||||
[false, 1],
|
[false, 1],
|
||||||
@@ -201,6 +205,7 @@ describe("Dataframe column summary", () => {
|
|||||||
[1, false, "0", "0"],
|
[1, false, "0", "0"],
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex([
|
new Dataframe.KeyIndex([
|
||||||
"name",
|
"name",
|
||||||
"nameString",
|
"nameString",
|
||||||
@@ -269,6 +274,7 @@ describe("Dataframe column summary", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
categorical: true,
|
categorical: true,
|
||||||
categories: expect.arrayContaining([1, false, "0"]),
|
categories: expect.arrayContaining([1, false, "0"]),
|
||||||
|
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
|
||||||
categoryCounts: new Map([
|
categoryCounts: new Map([
|
||||||
[1, 1],
|
[1, 1],
|
||||||
[false, 1],
|
[false, 1],
|
||||||
+5
-2
@@ -1,7 +1,8 @@
|
|||||||
import PromiseLimit from "../../src/util/promiseLimit";
|
import PromiseLimit from "../../src/util/promiseLimit";
|
||||||
import { range } from "../../src/util/range";
|
import { range } from "../../src/util/range";
|
||||||
|
|
||||||
const delay = (t) => new Promise((resolve) => setTimeout(resolve, t));
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const delay = (t: any) => new Promise((resolve) => setTimeout(resolve, t));
|
||||||
|
|
||||||
describe("PromiseLimit", () => {
|
describe("PromiseLimit", () => {
|
||||||
test("simple evaluation, concurrency 1", async () => {
|
test("simple evaluation, concurrency 1", async () => {
|
||||||
@@ -51,7 +52,9 @@ describe("PromiseLimit", () => {
|
|||||||
running -= 1;
|
running -= 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all(range(10).map((i) => plimit.add(() => callback(i))));
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
await Promise.all(range(10).map((i: any) => plimit.add(() => callback(i))));
|
||||||
|
|
||||||
expect(maxRunning).toEqual(2);
|
expect(maxRunning).toEqual(2);
|
||||||
});
|
});
|
||||||
@@ -6,14 +6,20 @@ describe("range", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("range(stop)", () => {
|
test("range(stop)", () => {
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
|
||||||
expect(range(3)).toMatchObject([0, 1, 2]);
|
expect(range(3)).toMatchObject([0, 1, 2]);
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
|
||||||
expect(range(0)).toMatchObject([]);
|
expect(range(0)).toMatchObject([]);
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 1.
|
||||||
expect(range(1)).toMatchObject([0]);
|
expect(range(1)).toMatchObject([0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("range(start,stop)", () => {
|
test("range(start,stop)", () => {
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
|
||||||
expect(range(0, 0)).toMatchObject([]);
|
expect(range(0, 0)).toMatchObject([]);
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
|
||||||
expect(range(0, 2)).toMatchObject([0, 1]);
|
expect(range(0, 2)).toMatchObject([0, 1]);
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 3 arguments, but got 2.
|
||||||
expect(range(4, 8)).toMatchObject([4, 5, 6, 7]);
|
expect(range(4, 8)).toMatchObject([4, 5, 6, 7]);
|
||||||
});
|
});
|
||||||
|
|
||||||
+21
-8
@@ -81,6 +81,7 @@ describe("categorical color helpers", () => {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
new Dataframe.KeyIndex(["continuousColumn", "categoricalColumn"])
|
new Dataframe.KeyIndex(["continuousColumn", "categoricalColumn"])
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -95,6 +96,7 @@ describe("categorical color helpers", () => {
|
|||||||
const data = obsDataframe.col("categoricalColumn").asArray();
|
const data = obsDataframe.col("categoricalColumn").asArray();
|
||||||
const cats = schema.annotations.obsByName.categoricalColumn.categories;
|
const cats = schema.annotations.obsByName.categoricalColumn.categories;
|
||||||
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
|
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
|
||||||
|
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
|
||||||
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
|
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -112,6 +114,7 @@ describe("categorical color helpers", () => {
|
|||||||
const data = obsDataframe.col("categoricalColumn").asArray();
|
const data = obsDataframe.col("categoricalColumn").asArray();
|
||||||
const cats = schemaClone.annotations.obsByName.categoricalColumn.categories;
|
const cats = schemaClone.annotations.obsByName.categoricalColumn.categories;
|
||||||
for (let i = 0; i < schemaClone.dataframe.nObs; i += 1) {
|
for (let i = 0; i < schemaClone.dataframe.nObs; i += 1) {
|
||||||
|
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
|
||||||
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
|
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -122,7 +125,8 @@ describe("categorical color helpers", () => {
|
|||||||
Array.from(schema.annotations.obsByName.categoricalColumn.categories)
|
Array.from(schema.annotations.obsByName.categoricalColumn.categories)
|
||||||
);
|
);
|
||||||
const userDefinedColorTable = {
|
const userDefinedColorTable = {
|
||||||
categoricalColumn: shuffleCats.reduce((acc, label) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
categoricalColumn: shuffleCats.reduce((acc: any, label: any) => {
|
||||||
acc[label] = randRGBColor();
|
acc[label] = randRGBColor();
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
@@ -136,12 +140,14 @@ describe("categorical color helpers", () => {
|
|||||||
"categoricalColumn",
|
"categoricalColumn",
|
||||||
obsDataframe,
|
obsDataframe,
|
||||||
schema,
|
schema,
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{}' is not assignable to paramet... Remove this comment to see the full error message
|
||||||
userColors
|
userColors
|
||||||
);
|
);
|
||||||
expect(ct).toBeDefined();
|
expect(ct).toBeDefined();
|
||||||
const data = obsDataframe.col("categoricalColumn").asArray();
|
const data = obsDataframe.col("categoricalColumn").asArray();
|
||||||
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
|
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
|
||||||
expect(makeScale(ct.rgb[i])).toEqual(
|
expect(makeScale(ct.rgb[i])).toEqual(
|
||||||
|
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
|
||||||
ct.scale(cats.indexOf(data[i])).toString()
|
ct.scale(cats.indexOf(data[i])).toString()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -154,31 +160,38 @@ TODO:
|
|||||||
2. user defined colors
|
2. user defined colors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function indexSchema(schema) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function indexSchema(schema: any) {
|
||||||
schema.annotations.obsByName = Object.fromEntries(
|
schema.annotations.obsByName = Object.fromEntries(
|
||||||
schema.annotations?.obs?.columns?.map((v) => [v.name, v]) ?? []
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
schema.annotations?.obs?.columns?.map((v: any) => [v.name, v]) ?? []
|
||||||
);
|
);
|
||||||
schema.annotations.varByName = Object.fromEntries(
|
schema.annotations.varByName = Object.fromEntries(
|
||||||
schema.annotations?.var?.columns?.map((v) => [v.name, v]) ?? []
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
schema.annotations?.var?.columns?.map((v: any) => [v.name, v]) ?? []
|
||||||
);
|
);
|
||||||
schema.layout.obsByName = Object.fromEntries(
|
schema.layout.obsByName = Object.fromEntries(
|
||||||
schema.layout?.obs?.map((v) => [v.name, v]) ?? []
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
schema.layout?.obs?.map((v: any) => [v.name, v]) ?? []
|
||||||
);
|
);
|
||||||
schema.layout.varByName = Object.fromEntries(
|
schema.layout.varByName = Object.fromEntries(
|
||||||
schema.layout?.var?.map((v) => [v.name, v]) ?? []
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
schema.layout?.var?.map((v: any) => [v.name, v]) ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeScale(rgb) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function makeScale(rgb: any) {
|
||||||
// make a scale string from a rgb float triple
|
// make a scale string from a rgb float triple
|
||||||
return `rgb(${(rgb[0] * 255) >>> 0}, ${(rgb[1] * 255) >>> 0}, ${
|
return `rgb(${(rgb[0] * 255) >>> 0}, ${(rgb[1] * 255) >>> 0}, ${
|
||||||
(rgb[2] * 256) >>> 0
|
(rgb[2] * 256) >>> 0
|
||||||
})`;
|
})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffle(array) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function shuffle(array: any) {
|
||||||
for (let i = array.length - 1; i > 0; i -= 1) {
|
for (let i = array.length - 1; i > 0; i -= 1) {
|
||||||
const j = (Math.random() * (i + 1)) >>> 0;
|
const j = (Math.random() * (i + 1)) >>> 0;
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
test controls helpers
|
test controls helpers
|
||||||
*/
|
*/
|
||||||
// TODO #2227 test: improve test coverage on control helper functions
|
// TODO #2227 test: improve test coverage on control helper functions
|
||||||
// (`topNCategories()`, `isSelectableCategoryName()`, `selectableCategoryNames()`, `createCategorySummaryFromDfCol()`, `createCategoricalSelection()`, )
|
// (`isSelectableCategoryName()`, `selectableCategoryNames()`, `createCategorySummaryFromDfCol()`, `createCategoricalSelection()`, )
|
||||||
|
|
||||||
describe("controls helpers", () => {
|
describe("controls helpers", () => {
|
||||||
test("placeholder", () => {});
|
test("placeholder", () => {});
|
||||||
+1
@@ -24,6 +24,7 @@ describe("encode/decode", () => {
|
|||||||
expect(dfA.columns).toEqual(columns);
|
expect(dfA.columns).toEqual(columns);
|
||||||
|
|
||||||
const colIndex = new KeyIndex(["a", "b", "c", "d"]);
|
const colIndex = new KeyIndex(["a", "b", "c", "d"]);
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
|
||||||
const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex);
|
const dfWithColIdx = new Dataframe([3, 4], columns, null, colIndex);
|
||||||
const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx));
|
const dfB = decodeMatrixFBS(encodeMatrixFBS(dfWithColIdx));
|
||||||
expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims);
|
expect([dfB.nRows, dfB.nCols]).toEqual(dfWithColIdx.dims);
|
||||||
+13
-2
@@ -75,6 +75,7 @@ const aSchemaResponse = {
|
|||||||
|
|
||||||
const anAnnotationsObsJSONResponse = {
|
const anAnnotationsObsJSONResponse = {
|
||||||
names: ["name", "field1", "field2", "field3", "field4"],
|
names: ["name", "field1", "field2", "field3", "field4"],
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||||
data: _()
|
data: _()
|
||||||
.range(nObs)
|
.range(nObs)
|
||||||
.map((idx) => [
|
.map((idx) => [
|
||||||
@@ -91,6 +92,7 @@ const anAnnotationsObsJSONResponse = {
|
|||||||
|
|
||||||
const anAnnotationsVarJSONResponse = {
|
const anAnnotationsVarJSONResponse = {
|
||||||
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
|
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||||
data: _()
|
data: _()
|
||||||
.range(nVar)
|
.range(nVar)
|
||||||
.map((idx) => [
|
.map((idx) => [
|
||||||
@@ -105,8 +107,11 @@ const anAnnotationsVarJSONResponse = {
|
|||||||
.value(),
|
.value(),
|
||||||
};
|
};
|
||||||
|
|
||||||
function encodeTypedArray(builder, uType, uData) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function encodeTypedArray(builder: any, uType: any, uData: any) {
|
||||||
|
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
|
||||||
const uTypeName = NetEncoding.TypedArray[uType];
|
const uTypeName = NetEncoding.TypedArray[uType];
|
||||||
|
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
|
||||||
const ArrayType = NetEncoding[uTypeName];
|
const ArrayType = NetEncoding[uTypeName];
|
||||||
const dv = ArrayType.createDataVector(builder, uData);
|
const dv = ArrayType.createDataVector(builder, uData);
|
||||||
builder.startObject(1);
|
builder.startObject(1);
|
||||||
@@ -114,7 +119,8 @@ function encodeTypedArray(builder, uType, uData) {
|
|||||||
return builder.endObject();
|
return builder.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeMatrix(columns, colIndex = undefined) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function encodeMatrix(columns: any, colIndex = undefined) {
|
||||||
/*
|
/*
|
||||||
IMPORTANT: this is not a general purpose encoder. in particular,
|
IMPORTANT: this is not a general purpose encoder. in particular,
|
||||||
it doesn't correctly handle all column index types, nor does it
|
it doesn't correctly handle all column index types, nor does it
|
||||||
@@ -123,6 +129,7 @@ function encodeMatrix(columns, colIndex = undefined) {
|
|||||||
encodeMatrixFBS in matrix.py is more general. This is used only
|
encodeMatrixFBS in matrix.py is more general. This is used only
|
||||||
as a testing santity check (alt implementation).
|
as a testing santity check (alt implementation).
|
||||||
*/
|
*/
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||||
const utf8Encoder = new TextEncoder("utf-8");
|
const utf8Encoder = new TextEncoder("utf-8");
|
||||||
const builder = new flatbuffers.Builder(1024);
|
const builder = new flatbuffers.Builder(1024);
|
||||||
const cols = map(columns, (carr) => {
|
const cols = map(columns, (carr) => {
|
||||||
@@ -172,11 +179,13 @@ function encodeMatrix(columns, colIndex = undefined) {
|
|||||||
|
|
||||||
const anAnnotationsObsFBSResponse = (() => {
|
const anAnnotationsObsFBSResponse = (() => {
|
||||||
const columns = zip(...anAnnotationsObsJSONResponse.data).slice(1);
|
const columns = zip(...anAnnotationsObsJSONResponse.data).slice(1);
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
return encodeMatrix(columns, anAnnotationsObsJSONResponse.names);
|
return encodeMatrix(columns, anAnnotationsObsJSONResponse.names);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const anAnnotationsVarFBSResponse = (() => {
|
const anAnnotationsVarFBSResponse = (() => {
|
||||||
const columns = zip(...anAnnotationsVarJSONResponse.data).slice(1);
|
const columns = zip(...anAnnotationsVarJSONResponse.data).slice(1);
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
return encodeMatrix(columns, anAnnotationsVarJSONResponse.names);
|
return encodeMatrix(columns, anAnnotationsVarJSONResponse.names);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -185,11 +194,13 @@ const aLayoutFBSResponse = (() => {
|
|||||||
new Float32Array(nObs).fill(Math.random()),
|
new Float32Array(nObs).fill(Math.random()),
|
||||||
new Float32Array(nObs).fill(Math.random()),
|
new Float32Array(nObs).fill(Math.random()),
|
||||||
];
|
];
|
||||||
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
|
||||||
return encodeMatrix(coords, ["umap_0", "umap_1"]);
|
return encodeMatrix(coords, ["umap_0", "umap_1"]);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const aDataObsResponse = {
|
const aDataObsResponse = {
|
||||||
var: [2, 4, 29],
|
var: [2, 4, 29],
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
|
||||||
obs: _()
|
obs: _()
|
||||||
.range(nObs)
|
.range(nObs)
|
||||||
.map((idx) => [idx, Math.random(), Math.random(), Math.random()])
|
.map((idx) => [idx, Math.random(), Math.random(), Math.random()])
|
||||||
+60
-16
@@ -126,7 +126,8 @@ const someData = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let payments = null;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let payments: any = null;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
payments = new Crossfilter(someData);
|
payments = new Crossfilter(someData);
|
||||||
});
|
});
|
||||||
@@ -138,7 +139,13 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
expect(payments.all()).toEqual(someData);
|
expect(payments.all()).toEqual(someData);
|
||||||
|
|
||||||
const p = payments
|
const p = payments
|
||||||
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
|
.addDimension(
|
||||||
|
"quantity",
|
||||||
|
"scalar",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, d: any) => d[i].quantity,
|
||||||
|
Int32Array
|
||||||
|
)
|
||||||
.select("quantity", { mode: "all" });
|
.select("quantity", { mode: "all" });
|
||||||
expect(p).toBeDefined();
|
expect(p).toBeDefined();
|
||||||
expect(p.all()).toEqual(someData);
|
expect(p.all()).toEqual(someData);
|
||||||
@@ -158,7 +165,8 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
const p2 = payments.addDimension(
|
const p2 = payments.addDimension(
|
||||||
"quantity",
|
"quantity",
|
||||||
"scalar",
|
"scalar",
|
||||||
(i, data) => data[i].quantity,
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, data: any) => data[i].quantity,
|
||||||
Int32Array
|
Int32Array
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -175,10 +183,22 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
|
|
||||||
test("select all and none", () => {
|
test("select all and none", () => {
|
||||||
let p = payments
|
let p = payments
|
||||||
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
|
.addDimension(
|
||||||
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
|
"quantity",
|
||||||
.addDimension("total", "scalar", (i, d) => d[i].total, Float32Array)
|
"scalar",
|
||||||
.addDimension("type", "enum", (i, d) => d[i].type);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, d: any) => d[i].quantity,
|
||||||
|
Int32Array
|
||||||
|
) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
.addDimension("tip", "scalar", (i: any, d: any) => d[i].tip, Float32Array)
|
||||||
|
.addDimension(
|
||||||
|
"total",
|
||||||
|
"scalar",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, d: any) => d[i].total,
|
||||||
|
Float32Array
|
||||||
|
) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
.addDimension("type", "enum", (i: any, d: any) => d[i].type);
|
||||||
expect(p).toBeDefined();
|
expect(p).toBeDefined();
|
||||||
|
|
||||||
/* expect all records to be selected - default init state */
|
/* expect all records to be selected - default init state */
|
||||||
@@ -230,11 +250,24 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("scalar dimension", () => {
|
describe("scalar dimension", () => {
|
||||||
let p;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let p: any;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
p = payments
|
p = payments
|
||||||
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
|
.addDimension(
|
||||||
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
|
"quantity",
|
||||||
|
"scalar",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, d: any) => d[i].quantity,
|
||||||
|
Int32Array
|
||||||
|
)
|
||||||
|
.addDimension(
|
||||||
|
"tip",
|
||||||
|
"scalar",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, d: any) => d[i].tip,
|
||||||
|
Float32Array
|
||||||
|
)
|
||||||
.select("tip", { mode: "all" });
|
.select("tip", { mode: "all" });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -277,9 +310,11 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("enum dimension", () => {
|
describe("enum dimension", () => {
|
||||||
let p;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let p: any;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
p = payments.addDimension("type", "enum", (i, d) => d[i].type);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
p = payments.addDimension("type", "enum", (i: any, d: any) => d[i].type);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("all", () => {
|
test("all", () => {
|
||||||
@@ -317,7 +352,8 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("spatial dimension", () => {
|
describe("spatial dimension", () => {
|
||||||
let p;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let p: any;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const X = someData.map((r) => r.coords[0]);
|
const X = someData.map((r) => r.coords[0]);
|
||||||
const Y = someData.map((r) => r.coords[1]);
|
const Y = someData.map((r) => r.coords[1]);
|
||||||
@@ -406,14 +442,22 @@ describe("ImmutableTypedCrossfilter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("non-finite scalars", () => {
|
describe("non-finite scalars", () => {
|
||||||
let p;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
let p: any;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
p = payments
|
p = payments
|
||||||
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
|
.addDimension(
|
||||||
|
"quantity",
|
||||||
|
"scalar",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, d: any) => d[i].quantity,
|
||||||
|
Int32Array
|
||||||
|
)
|
||||||
.addDimension(
|
.addDimension(
|
||||||
"nonFinite",
|
"nonFinite",
|
||||||
"scalar",
|
"scalar",
|
||||||
(i, d) => d[i].nonFinite,
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(i: any, d: any) => d[i].nonFinite,
|
||||||
Float32Array
|
Float32Array
|
||||||
)
|
)
|
||||||
.select("quantity", { mode: "all" });
|
.select("quantity", { mode: "all" });
|
||||||
+18
-5
@@ -15,7 +15,8 @@ paths for:
|
|||||||
const pInf = Number.POSITIVE_INFINITY;
|
const pInf = Number.POSITIVE_INFINITY;
|
||||||
const nInf = Number.NEGATIVE_INFINITY;
|
const nInf = Number.NEGATIVE_INFINITY;
|
||||||
|
|
||||||
function fillRange(arr, start = 0) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function fillRange(arr: any, start = 0) {
|
||||||
const larr = arr;
|
const larr = arr;
|
||||||
for (let i = 0, len = larr.length; i < len; i += 1) {
|
for (let i = 0, len = larr.length; i < len; i += 1) {
|
||||||
larr[i] = i + start;
|
larr[i] = i + start;
|
||||||
@@ -23,7 +24,8 @@ function fillRange(arr, start = 0) {
|
|||||||
return larr;
|
return larr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillRand(arr) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function fillRand(arr: any) {
|
||||||
for (let i = 0, len = arr.length; i < len; i += 1) {
|
for (let i = 0, len = arr.length; i < len; i += 1) {
|
||||||
arr[i] = Math.random();
|
arr[i] = Math.random();
|
||||||
}
|
}
|
||||||
@@ -48,16 +50,22 @@ describe("sortArray", () => {
|
|||||||
describe("finite numbers", () => {
|
describe("finite numbers", () => {
|
||||||
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
|
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
|
||||||
test(Type.name, () => {
|
test(Type.name, () => {
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject(
|
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject(
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
Type.from([0, 1, 2, 3, 4, 5, 6])
|
Type.from([0, 1, 2, 3, 4, 5, 6])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1]))).toMatchObject(
|
expect(sortArray(Type.from([6, 5, 4, 3, 2, 1]))).toMatchObject(
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
Type.from([1, 2, 3, 4, 5, 6])
|
Type.from([1, 2, 3, 4, 5, 6])
|
||||||
);
|
);
|
||||||
|
|
||||||
const source = fillRand(new Type(1000));
|
const source = fillRand(new Type(1000));
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
expect(sortArray(Type.from(source))).toMatchObject(
|
expect(sortArray(Type.from(source))).toMatchObject(
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
Type.from(source).sort()
|
Type.from(source).sort()
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -130,22 +138,27 @@ describe("sortIndex", () => {
|
|||||||
describe("finite numbers", () => {
|
describe("finite numbers", () => {
|
||||||
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
|
[Array, Float32Array, Uint32Array, Int32Array, Float64Array].map((Type) =>
|
||||||
test(Type.name, () => {
|
test(Type.name, () => {
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]);
|
const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]);
|
||||||
const index1 = fillRange(new Uint32Array(source1.length));
|
const index1 = fillRange(new Uint32Array(source1.length));
|
||||||
expect(sortIndex(index1, source1)).toMatchObject(
|
expect(sortIndex(index1, source1)).toMatchObject(
|
||||||
index1.sort((a, b) => source1[a] - source1[b])
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
index1.sort((a: any, b: any) => source1[a] - source1[b])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.
|
||||||
const source2 = Type.from([6, 5, 4, 3, 2, 1]);
|
const source2 = Type.from([6, 5, 4, 3, 2, 1]);
|
||||||
const index2 = fillRange(new Uint32Array(source2.length));
|
const index2 = fillRange(new Uint32Array(source2.length));
|
||||||
expect(sortIndex(index2, source2)).toMatchObject(
|
expect(sortIndex(index2, source2)).toMatchObject(
|
||||||
index2.sort((a, b) => source1[a] - source1[b])
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
index2.sort((a: any, b: any) => source1[a] - source1[b])
|
||||||
);
|
);
|
||||||
|
|
||||||
const source3 = fillRand(new Type(1000));
|
const source3 = fillRand(new Type(1000));
|
||||||
const index3 = fillRange(new Uint32Array(source3.length));
|
const index3 = fillRange(new Uint32Array(source3.length));
|
||||||
expect(sortIndex(index3, source3)).toMatchObject(
|
expect(sortIndex(index3, source3)).toMatchObject(
|
||||||
index3.sort((a, b) => source1[a] - source1[b])
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
index3.sort((a: any, b: any) => source1[a] - source1[b])
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -11,11 +11,13 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@babel/preset-react",
|
"@babel/preset-react",
|
||||||
|
"@babel/preset-typescript",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"@babel/plugin-proposal-function-bind",
|
"@babel/plugin-proposal-function-bind",
|
||||||
["@babel/plugin-proposal-decorators", { legacy: true }],
|
["@babel/plugin-proposal-decorators", { legacy: true }],
|
||||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||||
|
["@babel/plugin-proposal-private-methods", { loose: true }],
|
||||||
"@babel/plugin-proposal-export-namespace-from",
|
"@babel/plugin-proposal-export-namespace-from",
|
||||||
"@babel/plugin-proposal-optional-chaining",
|
"@babel/plugin-proposal-optional-chaining",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@babel/preset-react",
|
"@babel/preset-react",
|
||||||
|
"@babel/preset-typescript",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"@babel/plugin-proposal-function-bind",
|
"@babel/plugin-proposal-function-bind",
|
||||||
["@babel/plugin-proposal-decorators", { legacy: true }],
|
["@babel/plugin-proposal-decorators", { legacy: true }],
|
||||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||||
|
["@babel/plugin-proposal-private-methods", { loose: true }],
|
||||||
"@babel/plugin-proposal-export-namespace-from",
|
"@babel/plugin-proposal-export-namespace-from",
|
||||||
"@babel/plugin-transform-react-constant-elements",
|
"@babel/plugin-transform-react-constant-elements",
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: "babel-eslint",
|
parser: "@typescript-eslint/parser",
|
||||||
extends: [
|
extends: [
|
||||||
"airbnb",
|
"airbnb-typescript",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:eslint-comments/recommended",
|
"plugin:eslint-comments/recommended",
|
||||||
"plugin:@blueprintjs/recommended",
|
"plugin:@blueprintjs/recommended",
|
||||||
"plugin:compat/recommended",
|
"plugin:compat/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:jsx-a11y/recommended",
|
||||||
|
// (thuang) disable eslint formatting rules, so prettier can do its job
|
||||||
|
// Do not use `plugin:prettier/recommended` per doc below:
|
||||||
|
// https://prettier.io/docs/en/integrating-with-linters.html
|
||||||
"prettier",
|
"prettier",
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
@@ -32,18 +37,47 @@ module.exports = {
|
|||||||
jsx: true,
|
jsx: true,
|
||||||
generators: true,
|
generators: true,
|
||||||
},
|
},
|
||||||
|
project: "./tsconfig.json",
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"react/jsx-no-target-blank": "off",
|
"react/jsx-no-target-blank": "off",
|
||||||
"eslint-comments/require-description": ["error"],
|
"eslint-comments/require-description": ["error"],
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
|
"@typescript-eslint/no-magic-numbers": "off",
|
||||||
"no-nested-ternary": "off",
|
"no-nested-ternary": "off",
|
||||||
"func-style": "off",
|
"func-style": "off",
|
||||||
"arrow-parens": "off",
|
"arrow-parens": "off",
|
||||||
"no-use-before-define": "off",
|
"no-use-before-define": "off",
|
||||||
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
"react/jsx-filename-extension": "off",
|
"react/jsx-filename-extension": "off",
|
||||||
"comma-dangle": "off",
|
"comma-dangle": "off",
|
||||||
|
"@typescript-eslint/comma-dangle": "off",
|
||||||
"no-underscore-dangle": "off",
|
"no-underscore-dangle": "off",
|
||||||
|
// Override airbnb config to allow leading underscore
|
||||||
|
// https://github.com/iamturns/eslint-config-airbnb-typescript/blob/master/lib/shared.js#L35
|
||||||
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "class",
|
||||||
|
format: ["PascalCase"],
|
||||||
|
leadingUnderscore: "allow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "function",
|
||||||
|
format: ["camelCase", "PascalCase"],
|
||||||
|
leadingUnderscore: "allowSingleOrDouble",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "typeLike",
|
||||||
|
format: ["PascalCase"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "variable",
|
||||||
|
format: ["camelCase", "PascalCase", "UPPER_CASE"],
|
||||||
|
leadingUnderscore: "allowSingleOrDouble",
|
||||||
|
trailingUnderscore: "allowDouble",
|
||||||
|
},
|
||||||
|
],
|
||||||
"implicit-arrow-linebreak": "off",
|
"implicit-arrow-linebreak": "off",
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"spaced-comment": ["error", "always", { exceptions: ["*"] }],
|
"spaced-comment": ["error", "always", { exceptions: ["*"] }],
|
||||||
@@ -51,6 +85,7 @@ module.exports = {
|
|||||||
"object-curly-newline": ["error", { consistent: true }],
|
"object-curly-newline": ["error", { consistent: true }],
|
||||||
"react/prop-types": [0],
|
"react/prop-types": [0],
|
||||||
"space-before-function-paren": "off",
|
"space-before-function-paren": "off",
|
||||||
|
"@typescript-eslint/space-before-function-paren": "off",
|
||||||
"function-paren-newline": "off",
|
"function-paren-newline": "off",
|
||||||
"prefer-destructuring": ["error", { object: true, array: false }],
|
"prefer-destructuring": ["error", { object: true, array: false }],
|
||||||
"import/prefer-default-export": "off",
|
"import/prefer-default-export": "off",
|
||||||
@@ -69,9 +104,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["**/*.test.js"],
|
files: ["**/*.test.ts"],
|
||||||
env: {
|
env: {
|
||||||
jest: true, // now **/*.test.js files' env has both es6 *and* jest
|
jest: true, // now **/*.test.ts files' env has both es6 *and* jest
|
||||||
},
|
},
|
||||||
// Can't extend in overrides: https://github.com/eslint/eslint/issues/8813
|
// Can't extend in overrides: https://github.com/eslint/eslint/issues/8813
|
||||||
// "extends": ["plugin:jest/recommended"]
|
// "extends": ["plugin:jest/recommended"]
|
||||||
@@ -86,3 +121,4 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"*.js": "eslint --fix",
|
"*.{js,ts,jsx,tsx}": "eslint --fix",
|
||||||
|
"**/*": "prettier --write --ignore-unknown",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies -- this file is a devDependency*/
|
/* eslint-disable import/no-extraneous-dependencies -- this file is a devDependency*/
|
||||||
|
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const cheerio = require("cheerio");
|
const cheerio = require("cheerio");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
|
|
||||||
const digest = (str) => {
|
const digest = (str) => {
|
||||||
@@ -50,4 +54,5 @@ class CspHashPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CspHashPlugin;
|
module.exports = CspHashPlugin;
|
||||||
|
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
/* eslint-enable import/no-extraneous-dependencies -- enable*/
|
/* eslint-enable import/no-extraneous-dependencies -- enable*/
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/cellxgene-logo.png"
|
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/cellxgene-logo.png"
|
||||||
style="width: 320px;"
|
style="width: 320px"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
style="
|
style="
|
||||||
@@ -37,36 +37,34 @@
|
|||||||
max-width: 550px;
|
max-width: 550px;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div style="margin-bottom: 0; font-weight: bolder; font-size: 1.2em;">
|
<div style="margin-bottom: 0; font-weight: bolder; font-size: 1.2em">
|
||||||
Unsupported Browser
|
Unsupported Browser
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 0;">
|
<div style="margin-top: 0">
|
||||||
cellxgene is currently supported on the following browsers
|
cellxgene is currently supported on the following browsers
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div style="display: flex; justify-content: space-around; margin-top: 16px">
|
||||||
style="display: flex; justify-content: space-around; margin-top: 16px;"
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
href="https://www.google.com/chrome/?hl=en%22"
|
href="https://www.google.com/chrome/?hl=en%22"
|
||||||
aria-label="Download Google Chrome"
|
aria-label="Download Google Chrome"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/chrome.png"
|
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/chrome.png"
|
||||||
style="width: 80px; height: 80px;"
|
style="width: 80px; height: 80px"
|
||||||
/>
|
/>
|
||||||
<div>Chrome > 60</div>
|
<div>Chrome > 60</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox">
|
<a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox">
|
||||||
<img
|
<img
|
||||||
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/firefox.png"
|
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/firefox.png"
|
||||||
style="width: 80px; height: 80px;"
|
style="width: 80px; height: 80px"
|
||||||
/>
|
/>
|
||||||
<div>Firefox ≥ 60</div>
|
<div>Firefox ≥ 60</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="//www.microsoft.com/edge" aria-label="Download Edge">
|
<a href="//www.microsoft.com/edge" aria-label="Download Edge">
|
||||||
<img
|
<img
|
||||||
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png"
|
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png"
|
||||||
style="width: 80px; height: 80px;"
|
style="width: 80px; height: 80px"
|
||||||
/>
|
/>
|
||||||
<div>Edge ≥ 79</div>
|
<div>Edge ≥ 79</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
|
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
|
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
|
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const { merge } = require("webpack-merge");
|
const { merge } = require("webpack-merge");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const sharedConfig = require("./webpack.config.shared");
|
const sharedConfig = require("./webpack.config.shared");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const babelOptions = require("../babel/babel.dev");
|
const babelOptions = require("../babel/babel.dev");
|
||||||
|
|
||||||
const fonts = path.resolve("src/fonts");
|
const fonts = path.resolve("src/fonts");
|
||||||
@@ -23,7 +33,7 @@ const devConfig = {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.(ts|js)x?$/,
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: babelOptions,
|
options: babelOptions,
|
||||||
},
|
},
|
||||||
@@ -83,3 +93,4 @@ const devConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = merge(sharedConfig, devConfig);
|
module.exports = merge(sharedConfig, devConfig);
|
||||||
|
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const TerserJSPlugin = require("terser-webpack-plugin");
|
const TerserJSPlugin = require("terser-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const CleanCss = require("clean-css");
|
const CleanCss = require("clean-css");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
|
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const { merge } = require("webpack-merge");
|
const { merge } = require("webpack-merge");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const babelOptions = require("../babel/babel.prod");
|
const babelOptions = require("../babel/babel.prod");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const CspHashPlugin = require("./cspHashPlugin");
|
const CspHashPlugin = require("./cspHashPlugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const sharedConfig = require("./webpack.config.shared");
|
const sharedConfig = require("./webpack.config.shared");
|
||||||
|
|
||||||
const fonts = path.resolve("src/fonts");
|
const fonts = path.resolve("src/fonts");
|
||||||
@@ -38,7 +51,7 @@ const prodConfig = {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.(ts|js)x?$/,
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: babelOptions,
|
options: babelOptions,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const ObsoleteWebpackPlugin = require("obsolete-webpack-plugin");
|
const ObsoleteWebpackPlugin = require("obsolete-webpack-plugin");
|
||||||
// eslint-disable-next-line @blueprintjs/classes-constants -- incorrect match
|
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
|
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
|
||||||
|
|
||||||
const src = path.resolve("src");
|
const src = path.resolve("src");
|
||||||
@@ -29,6 +34,9 @@ module.exports = {
|
|||||||
path: path.resolve("build"),
|
path: path.resolve("build"),
|
||||||
publicPath,
|
publicPath,
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: [".ts", ".tsx", "..."],
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -72,3 +80,4 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
|
||||||
|
|||||||
Generated
+12353
-10321
File diff suppressed because it is too large
Load Diff
+44
-15
@@ -8,9 +8,9 @@
|
|||||||
"build": "npm run clean && webpack --config",
|
"build": "npm run clean && webpack --config",
|
||||||
"clean": "rimraf build",
|
"clean": "rimraf build",
|
||||||
"dev": "npm run build -- configuration/webpack/webpack.config.dev.js",
|
"dev": "npm run build -- configuration/webpack/webpack.config.dev.js",
|
||||||
"e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js",
|
"e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts",
|
||||||
"e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.js",
|
"e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.ts",
|
||||||
"e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js",
|
"e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts",
|
||||||
"fmt": "eslint --fix src __tests__",
|
"fmt": "eslint --fix src __tests__",
|
||||||
"lint": "eslint --fix src __tests__",
|
"lint": "eslint --fix src __tests__",
|
||||||
"prod": "npm run build -- configuration/webpack/webpack.config.prod.js",
|
"prod": "npm run build -- configuration/webpack/webpack.config.prod.js",
|
||||||
@@ -64,11 +64,9 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-async": "^10.0.1",
|
"react-async": "^10.0.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-easy-emoji": "^1.4.0",
|
|
||||||
"react-flip-toolkit": "^7.0.12",
|
"react-flip-toolkit": "^7.0.12",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-icons": "^4.2.0",
|
"react-icons": "^4.2.0",
|
||||||
"react-popper": "^2.2.4",
|
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
@@ -90,10 +88,38 @@
|
|||||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||||
"@babel/preset-env": "^7.13.15",
|
"@babel/preset-env": "^7.13.15",
|
||||||
"@babel/preset-react": "^7.13.13",
|
"@babel/preset-react": "^7.13.13",
|
||||||
|
"@babel/preset-typescript": "^7.14.5",
|
||||||
"@babel/register": "^7.13.16",
|
"@babel/register": "^7.13.16",
|
||||||
"@babel/runtime": "^7.13.16",
|
"@babel/runtime": "^7.13.16",
|
||||||
"@blueprintjs/eslint-plugin": "^0.3.0",
|
"@blueprintjs/eslint-plugin": "^0.3.0",
|
||||||
"@sentry/webpack-plugin": "^1.15.0",
|
"@sentry/webpack-plugin": "^1.15.0",
|
||||||
|
"@types/d3": "^7.0.0",
|
||||||
|
"@types/d3-scale-chromatic": "^3.0.0",
|
||||||
|
"@types/expect-puppeteer": "^4.4.6",
|
||||||
|
"@types/flatbuffers": "^1.10.0",
|
||||||
|
"@types/is-number": "^7.0.1",
|
||||||
|
"@types/jest": "^26.0.24",
|
||||||
|
"@types/jest-environment-puppeteer": "^4.4.1",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.6",
|
||||||
|
"@types/lodash.difference": "^4.5.6",
|
||||||
|
"@types/lodash.every": "^4.6.6",
|
||||||
|
"@types/lodash.filter": "^4.6.6",
|
||||||
|
"@types/lodash.foreach": "^4.5.6",
|
||||||
|
"@types/lodash.isnumber": "^3.0.6",
|
||||||
|
"@types/lodash.map": "^4.6.13",
|
||||||
|
"@types/lodash.pull": "^4.1.6",
|
||||||
|
"@types/lodash.sortby": "^4.7.6",
|
||||||
|
"@types/lodash.uniq": "^4.5.6",
|
||||||
|
"@types/lodash.zip": "^4.2.6",
|
||||||
|
"@types/pako": "^1.0.2",
|
||||||
|
"@types/puppeteer": "^5.4.4",
|
||||||
|
"@types/react": "^17.0.14",
|
||||||
|
"@types/react-dom": "^17.0.9",
|
||||||
|
"@types/react-helmet": "^6.1.2",
|
||||||
|
"@types/react-redux": "^7.1.18",
|
||||||
|
"@types/sha1": "^1.1.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.28.4",
|
||||||
|
"@typescript-eslint/parser": "^4.28.4",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-jest": "^26.1.0",
|
"babel-jest": "^26.1.0",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
@@ -103,10 +129,9 @@
|
|||||||
"clean-css": "^5.1.2",
|
"clean-css": "^5.1.2",
|
||||||
"clean-webpack-plugin": "^4.0.0-alpha.0",
|
"clean-webpack-plugin": "^4.0.0-alpha.0",
|
||||||
"codecov": "^3.7.1",
|
"codecov": "^3.7.1",
|
||||||
"connect-history-api-fallback": "^1.6.0",
|
|
||||||
"css-loader": "^5.2.4",
|
"css-loader": "^5.2.4",
|
||||||
"eslint": "^7.24.0",
|
"eslint": "^7.24.0",
|
||||||
"eslint-config-airbnb": "^18.2.0",
|
"eslint-config-airbnb-typescript": "^12.3.1",
|
||||||
"eslint-config-prettier": "^8.2.0",
|
"eslint-config-prettier": "^8.2.0",
|
||||||
"eslint-loader": "^4.0.2",
|
"eslint-loader": "^4.0.2",
|
||||||
"eslint-plugin-compat": "^3.8.0",
|
"eslint-plugin-compat": "^3.8.0",
|
||||||
@@ -115,12 +140,11 @@
|
|||||||
"eslint-plugin-import": "^2.22.0",
|
"eslint-plugin-import": "^2.22.0",
|
||||||
"eslint-plugin-jest": "^24.3.5",
|
"eslint-plugin-jest": "^24.3.5",
|
||||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
|
||||||
"eslint-plugin-react": "^7.23.2",
|
"eslint-plugin-react": "^7.23.2",
|
||||||
"eslint-plugin-react-hooks": "^4.0.8",
|
"eslint-plugin-react-hooks": "^4.0.8",
|
||||||
"expect-puppeteer": "^5.0.0",
|
"expect-puppeteer": "^5.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"favicons": "^6.2.1",
|
"favicons": "^6.2.2",
|
||||||
"favicons-webpack-plugin": "^5.0.2",
|
"favicons-webpack-plugin": "^5.0.2",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"html-webpack-plugin": "^5.3.1",
|
||||||
@@ -146,10 +170,8 @@
|
|||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"script-ext-html-webpack-plugin": "^2.1.4",
|
"script-ext-html-webpack-plugin": "^2.1.4",
|
||||||
"serve-favicon": "^2.5.0",
|
"serve-favicon": "^2.5.0",
|
||||||
"style-loader": "^2.0.0",
|
|
||||||
"sw-precache-webpack-plugin": "^1.0.0",
|
|
||||||
"terser-webpack-plugin": "^5.1.1",
|
"terser-webpack-plugin": "^5.1.1",
|
||||||
"url-loader": "^4.1.0",
|
"typescript": "^4.3.5",
|
||||||
"webpack": "^5.34.0",
|
"webpack": "^5.34.0",
|
||||||
"webpack-cli": "^4.6.0",
|
"webpack-cli": "^4.6.0",
|
||||||
"webpack-dev-middleware": "^4.1.0",
|
"webpack-dev-middleware": "^4.1.0",
|
||||||
@@ -157,10 +179,10 @@
|
|||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"testMatch": [
|
"testMatch": [
|
||||||
"**/__tests__/**/?(*.)(spec|test).js?(x)"
|
"**/__tests__/**/?(*.)(spec|test).ts?(x)"
|
||||||
],
|
],
|
||||||
"setupFiles": [
|
"setupFiles": [
|
||||||
"./__tests__/setupMissingGlobals.js"
|
"./__tests__/setupMissingGlobals.ts"
|
||||||
],
|
],
|
||||||
"coverageDirectory": "./coverage/",
|
"coverageDirectory": "./coverage/",
|
||||||
"collectCoverage": true
|
"collectCoverage": true
|
||||||
@@ -170,7 +192,8 @@
|
|||||||
"test": {
|
"test": {
|
||||||
"presets": [
|
"presets": [
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
"@babel/preset-react"
|
"@babel/preset-react",
|
||||||
|
"@babel/preset-typescript"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@babel/plugin-proposal-function-bind",
|
"@babel/plugin-proposal-function-bind",
|
||||||
@@ -186,6 +209,12 @@
|
|||||||
"loose": true
|
"loose": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"@babel/plugin-proposal-private-methods",
|
||||||
|
{
|
||||||
|
"loose": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"@babel/plugin-proposal-export-namespace-from",
|
"@babel/plugin-proposal-export-namespace-from",
|
||||||
"@babel/plugin-transform-react-constant-elements",
|
"@babel/plugin-transform-react-constant-elements",
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
|
|||||||
const { isUserAnnotation } = AnnotationsHelpers;
|
const { isUserAnnotation } = AnnotationsHelpers;
|
||||||
|
|
||||||
export const annotationCreateCategoryAction = (
|
export const annotationCreateCategoryAction = (
|
||||||
newCategoryName,
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
categoryToDuplicate
|
newCategoryName: any,
|
||||||
) => async (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
categoryToDuplicate: any
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
Add a new user-created category to the obs annotations.
|
Add a new user-created category to the obs annotations.
|
||||||
|
|
||||||
@@ -89,9 +92,12 @@ export const annotationCreateCategoryAction = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const annotationRenameCategoryAction = (
|
export const annotationRenameCategoryAction = (
|
||||||
oldCategoryName,
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
newCategoryName
|
oldCategoryName: any,
|
||||||
) => (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
newCategoryName: any
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
Rename a user-created annotation category
|
Rename a user-created annotation category
|
||||||
*/
|
*/
|
||||||
@@ -124,9 +130,12 @@ export const annotationRenameCategoryAction = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const annotationDeleteCategoryAction = (categoryName) => (
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
dispatch,
|
export const annotationDeleteCategoryAction = (categoryName: any) => (
|
||||||
getState
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
) => {
|
) => {
|
||||||
/*
|
/*
|
||||||
Delete a user-created category
|
Delete a user-created category
|
||||||
@@ -149,10 +158,13 @@ export const annotationDeleteCategoryAction = (categoryName) => (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const annotationCreateLabelInCategory = (
|
export const annotationCreateLabelInCategory = (
|
||||||
categoryName,
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
labelName,
|
categoryName: any,
|
||||||
assignSelected
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
) => async (dispatch, getState) => {
|
labelName: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
assignSelected: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
Add a new label to a user-defined category. If assignSelected is true, assign
|
Add a new label to a user-defined category. If assignSelected is true, assign
|
||||||
the label to all currently selected cells.
|
the label to all currently selected cells.
|
||||||
@@ -188,9 +200,11 @@ export const annotationCreateLabelInCategory = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const annotationDeleteLabelFromCategory = (
|
export const annotationDeleteLabelFromCategory = (
|
||||||
categoryName,
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
labelName
|
categoryName: any,
|
||||||
) => async (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
labelName: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
delete a label from a user-defined category
|
delete a label from a user-defined category
|
||||||
*/
|
*/
|
||||||
@@ -218,10 +232,14 @@ export const annotationDeleteLabelFromCategory = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const annotationRenameLabelInCategory = (
|
export const annotationRenameLabelInCategory = (
|
||||||
categoryName,
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
oldLabelName,
|
categoryName: any,
|
||||||
newLabelName
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
) => async (dispatch, getState) => {
|
oldLabelName: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
newLabelName: any
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
label name change
|
label name change
|
||||||
*/
|
*/
|
||||||
@@ -255,9 +273,12 @@ export const annotationRenameLabelInCategory = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const annotationLabelCurrentSelection = (
|
export const annotationLabelCurrentSelection = (
|
||||||
categoryName,
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
labelName
|
categoryName: any,
|
||||||
) => async (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
labelName: any
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
set the label on all currently selected
|
set the label on all currently selected
|
||||||
*/
|
*/
|
||||||
@@ -284,13 +305,24 @@ export const annotationLabelCurrentSelection = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function writableAnnotations(annoMatrix) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
return annoMatrix.schema.annotations.obs.columns
|
function writableAnnotations(annoMatrix: any) {
|
||||||
.filter((s) => s.writable)
|
return (
|
||||||
.map((s) => s.name);
|
annoMatrix.schema.annotations.obs.columns
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
.filter((s: any) => s.writable)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
.map((s: any) => s.name)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const needToSaveObsAnnotations = (annoMatrix, lastSavedAnnoMatrix) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const needToSaveObsAnnotations = (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
annoMatrix: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
lastSavedAnnoMatrix: any
|
||||||
|
) => {
|
||||||
/*
|
/*
|
||||||
Return true if there are LIKELY user-defined annotation modifications between the two
|
Return true if there are LIKELY user-defined annotation modifications between the two
|
||||||
annoMatrices. Technically not an action creator, but intimately intertwined
|
annoMatrices. Technically not an action creator, but intimately intertwined
|
||||||
@@ -314,11 +346,18 @@ export const needToSaveObsAnnotations = (annoMatrix, lastSavedAnnoMatrix) => {
|
|||||||
|
|
||||||
// no schema changes; check for change in contents
|
// no schema changes; check for change in contents
|
||||||
return currentWritable.some(
|
return currentWritable.some(
|
||||||
(col) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col)
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(col: any) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const saveObsAnnotationsAction = () => async (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
/*
|
/*
|
||||||
Save the user-created obs annotations IF any have changed.
|
Save the user-created obs annotations IF any have changed.
|
||||||
*/
|
*/
|
||||||
@@ -388,7 +427,13 @@ export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const saveGenesetsAction = () => async (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const saveGenesetsAction = () => async (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
// bail if gene sets not available, or in readonly mode.
|
// bail if gene sets not available, or in readonly mode.
|
||||||
@@ -465,7 +510,7 @@ export const saveGenesetsAction = () => async (dispatch, getState) => {
|
|||||||
res,
|
res,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.all([
|
return await Promise.all([
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "autosave: genesets complete",
|
type: "autosave: genesets complete",
|
||||||
lastSavedGenesets: genesets,
|
lastSavedGenesets: genesets,
|
||||||
@@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
action creators related to embeddings choice
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
|
||||||
|
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export async function _switchEmbedding(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
prevAnnoMatrix: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
prevCrossfilter: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
newEmbeddingName: any
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
DRY helper used by embedding action creators
|
||||||
|
*/
|
||||||
|
const base = prevAnnoMatrix.base();
|
||||||
|
const embeddingDf = await base.fetch("emb", newEmbeddingName);
|
||||||
|
const annoMatrix = _setEmbeddingSubset(prevAnnoMatrix, embeddingDf);
|
||||||
|
const obsCrossfilter = await new AnnoMatrixObsCrossfilter(
|
||||||
|
annoMatrix,
|
||||||
|
prevCrossfilter.obsCrossfilter
|
||||||
|
).select("emb", newEmbeddingName, {
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
return [annoMatrix, obsCrossfilter];
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const layoutChoiceAction = (newLayoutChoice: any) => async (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
|
/*
|
||||||
|
On layout choice, make sure we have selected all on the previous layout, AND the new
|
||||||
|
layout.
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
annoMatrix: prevAnnoMatrix,
|
||||||
|
obsCrossfilter: prevCrossfilter,
|
||||||
|
} = getState();
|
||||||
|
const [annoMatrix, obsCrossfilter] = await _switchEmbedding(
|
||||||
|
prevAnnoMatrix,
|
||||||
|
prevCrossfilter,
|
||||||
|
newLayoutChoice
|
||||||
|
);
|
||||||
|
dispatch({
|
||||||
|
type: "set layout choice",
|
||||||
|
layoutChoice: newLayoutChoice,
|
||||||
|
obsCrossfilter,
|
||||||
|
annoMatrix,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -21,7 +21,13 @@ The behavior manifest in these action creators:
|
|||||||
Note that crossfilter indices are lazy created, as needed.
|
Note that crossfilter indices are lazy created, as needed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const genesetDelete = (genesetName) => (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const genesetDelete = (genesetName: any) => (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { genesets } = state;
|
const { genesets } = state;
|
||||||
const gs = genesets?.genesets?.get(genesetName) ?? {};
|
const gs = genesets?.genesets?.get(genesetName) ?? {};
|
||||||
@@ -40,9 +46,12 @@ export const genesetDelete = (genesetName) => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genesetAddGenes = (genesetName, genes) => async (
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
dispatch,
|
export const genesetAddGenes = (genesetName: any, genes: any) => async (
|
||||||
getState
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
) => {
|
) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
|
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
|
||||||
@@ -50,7 +59,8 @@ export const genesetAddGenes = (genesetName, genes) => async (
|
|||||||
const varIndex = schema.annotations.var.index;
|
const varIndex = schema.annotations.var.index;
|
||||||
const df = await annoMatrix.fetch("var", varIndex);
|
const df = await annoMatrix.fetch("var", varIndex);
|
||||||
const geneNames = df.col(varIndex).asArray();
|
const geneNames = df.col(varIndex).asArray();
|
||||||
genes = genes.reduce((acc, gene) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
genes = genes.reduce((acc: any, gene: any) => {
|
||||||
if (geneNames.indexOf(gene.geneSymbol) === -1) {
|
if (geneNames.indexOf(gene.geneSymbol) === -1) {
|
||||||
postUserErrorToast(
|
postUserErrorToast(
|
||||||
`${gene.geneSymbol} doesn't appear to be a valid gene name.`
|
`${gene.geneSymbol} doesn't appear to be a valid gene name.`
|
||||||
@@ -78,9 +88,12 @@ export const genesetAddGenes = (genesetName, genes) => async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genesetDeleteGenes = (genesetName, geneSymbols) => (
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
dispatch,
|
export const genesetDeleteGenes = (genesetName: any, geneSymbols: any) => (
|
||||||
getState
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
) => {
|
) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols);
|
const obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols);
|
||||||
@@ -97,7 +110,14 @@ export const genesetDeleteGenes = (genesetName, geneSymbols) => (
|
|||||||
Private
|
Private
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) {
|
function dropGenesetSummaryDimension(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
obsCrossfilter: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
state: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
genesetName: any
|
||||||
|
) {
|
||||||
const { annoMatrix, genesets } = state;
|
const { annoMatrix, genesets } = state;
|
||||||
const varIndex = annoMatrix.schema.annotations?.var?.index;
|
const varIndex = annoMatrix.schema.annotations?.var?.index;
|
||||||
const gs = genesets?.genesets?.get(genesetName) ?? {};
|
const gs = genesets?.genesets?.get(genesetName) ?? {};
|
||||||
@@ -113,7 +133,8 @@ function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) {
|
|||||||
return obsCrossfilter.dropDimension("X", query);
|
return obsCrossfilter.dropDimension("X", query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dropGeneDimension(obsCrossfilter, state, gene) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function dropGeneDimension(obsCrossfilter: any, state: any, gene: any) {
|
||||||
const { annoMatrix } = state;
|
const { annoMatrix } = state;
|
||||||
const varIndex = annoMatrix.schema.annotations?.var?.index;
|
const varIndex = annoMatrix.schema.annotations?.var?.index;
|
||||||
const query = {
|
const query = {
|
||||||
@@ -126,10 +147,21 @@ function dropGeneDimension(obsCrossfilter, state, gene) {
|
|||||||
return obsCrossfilter.dropDimension("X", query);
|
return obsCrossfilter.dropDimension("X", query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dropGeneset(dispatch, state, genesetName, geneSymbols) {
|
function dropGeneset(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
state: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
genesetName: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
geneSymbols: any
|
||||||
|
) {
|
||||||
const { obsCrossfilter: prevObsCrossfilter } = state;
|
const { obsCrossfilter: prevObsCrossfilter } = state;
|
||||||
const obsCrossfilter = geneSymbols.reduce(
|
const obsCrossfilter = geneSymbols.reduce(
|
||||||
(crossfilter, gene) => dropGeneDimension(crossfilter, state, gene),
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(crossfilter: any, gene: any) =>
|
||||||
|
dropGeneDimension(crossfilter, state, gene),
|
||||||
dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName)
|
dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName)
|
||||||
);
|
);
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -137,7 +169,8 @@ function dropGeneset(dispatch, state, genesetName, geneSymbols) {
|
|||||||
continuousNamespace: { isGeneSetSummary: true },
|
continuousNamespace: { isGeneSetSummary: true },
|
||||||
selection: genesetName,
|
selection: genesetName,
|
||||||
});
|
});
|
||||||
geneSymbols.forEach((g) =>
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
geneSymbols.forEach((g: any) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "continuous metadata histogram cancel",
|
type: "continuous metadata histogram cancel",
|
||||||
continuousNamespace: { isUserDefined: true },
|
continuousNamespace: { isUserDefined: true },
|
||||||
@@ -12,10 +12,22 @@ import * as viewActions from "./viewStack";
|
|||||||
import * as embActions from "./embedding";
|
import * as embActions from "./embedding";
|
||||||
import * as genesetActions from "./geneset";
|
import * as genesetActions from "./geneset";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function setGlobalConfig(config: any) {
|
||||||
|
/**
|
||||||
|
* Set any global run-time config not _exclusively_ managed by the config reducer.
|
||||||
|
* This should only set fields defined in globals.globalConfig.
|
||||||
|
*/
|
||||||
|
globals.globalConfig.maxCategoricalOptionsToDisplay =
|
||||||
|
config?.parameters?.["max-category-items"] ??
|
||||||
|
globals.globalConfig.maxCategoricalOptionsToDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
return promise fetching user-configured colors
|
return promise fetching user-configured colors
|
||||||
*/
|
*/
|
||||||
async function userColorsFetchAndLoad(dispatch) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function userColorsFetchAndLoad(dispatch: any) {
|
||||||
return fetchJson("colors").then((response) =>
|
return fetchJson("colors").then((response) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "universe: user color load success",
|
type: "universe: user color load success",
|
||||||
@@ -28,9 +40,13 @@ async function schemaFetch() {
|
|||||||
return fetchJson("schema");
|
return fetchJson("schema");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configFetch(dispatch) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function configFetch(dispatch: any) {
|
||||||
return fetchJson("config").then((response) => {
|
return fetchJson("config").then((response) => {
|
||||||
const config = { ...globals.configDefaults, ...response.config };
|
const config = { ...globals.configDefaults, ...response.config };
|
||||||
|
|
||||||
|
setGlobalConfig(config);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "configuration load complete",
|
type: "configuration load complete",
|
||||||
config,
|
config,
|
||||||
@@ -39,7 +55,8 @@ async function configFetch(dispatch) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function userInfoFetch(dispatch) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function userInfoFetch(dispatch: any) {
|
||||||
return fetchJson("userinfo").then((response) => {
|
return fetchJson("userinfo").then((response) => {
|
||||||
const { userinfo: userInfo } = response || {};
|
const { userinfo: userInfo } = response || {};
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -50,7 +67,8 @@ async function userInfoFetch(dispatch) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function genesetsFetch(dispatch, config) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
async function genesetsFetch(dispatch: any, config: any) {
|
||||||
/* request genesets ONLY if the backend supports the feature */
|
/* request genesets ONLY if the backend supports the feature */
|
||||||
const defaultResponse = {
|
const defaultResponse = {
|
||||||
genesets: [],
|
genesets: [],
|
||||||
@@ -71,25 +89,31 @@ async function genesetsFetch(dispatch, config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function prefetchEmbeddings(annoMatrix) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function prefetchEmbeddings(annoMatrix: any) {
|
||||||
/*
|
/*
|
||||||
prefetch requests for all embeddings
|
prefetch requests for all embeddings
|
||||||
*/
|
*/
|
||||||
const { schema } = annoMatrix;
|
const { schema } = annoMatrix;
|
||||||
const available = schema.layout.obs.map((v) => v.name);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
available.forEach((embName) => annoMatrix.prefetch("emb", embName));
|
const available = schema.layout.obs.map((v: any) => v.name);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
available.forEach((embName: any) => annoMatrix.prefetch("emb", embName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Application bootstrap
|
Application bootstrap
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
const doInitialDataLoad = () =>
|
const doInitialDataLoad = () =>
|
||||||
catchErrorsWrap(async (dispatch) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
catchErrorsWrap(async (dispatch: any) => {
|
||||||
dispatch({ type: "initial data load start" });
|
dispatch({ type: "initial data load start" });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [config, schema] = await Promise.all([
|
const [config, schema] = await Promise.all([
|
||||||
configFetch(dispatch),
|
configFetch(dispatch),
|
||||||
|
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
|
||||||
schemaFetch(dispatch),
|
schemaFetch(dispatch),
|
||||||
userColorsFetchAndLoad(dispatch),
|
userColorsFetchAndLoad(dispatch),
|
||||||
userInfoFetch(dispatch),
|
userInfoFetch(dispatch),
|
||||||
@@ -113,7 +137,8 @@ const doInitialDataLoad = () =>
|
|||||||
const layoutSchema = schema?.schema?.layout?.obs ?? [];
|
const layoutSchema = schema?.schema?.layout?.obs ?? [];
|
||||||
if (
|
if (
|
||||||
defaultEmbedding &&
|
defaultEmbedding &&
|
||||||
layoutSchema.some((s) => s.name === defaultEmbedding)
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
layoutSchema.some((s: any) => s.name === defaultEmbedding)
|
||||||
) {
|
) {
|
||||||
dispatch(embActions.layoutChoiceAction(defaultEmbedding));
|
dispatch(embActions.layoutChoiceAction(defaultEmbedding));
|
||||||
}
|
}
|
||||||
@@ -122,21 +147,25 @@ const doInitialDataLoad = () =>
|
|||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
function requestSingleGeneExpressionCountsForColoringPOST(gene) {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function requestSingleGeneExpressionCountsForColoringPOST(gene: any) {
|
||||||
return {
|
return {
|
||||||
type: "color by expression",
|
type: "color by expression",
|
||||||
gene,
|
gene,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestUserDefinedGene = (gene) => ({
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const requestUserDefinedGene = (gene: any) => ({
|
||||||
type: "request user defined gene success",
|
type: "request user defined gene success",
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
genes: [gene],
|
genes: [gene],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatchDiffExpErrors = (dispatch, response) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const dispatchDiffExpErrors = (dispatch: any, response: any) => {
|
||||||
switch (response.status) {
|
switch (response.status) {
|
||||||
case 403:
|
case 403:
|
||||||
dispatchNetworkErrorMessageToUser(
|
dispatchNetworkErrorMessageToUser(
|
||||||
@@ -159,10 +188,14 @@ const dispatchDiffExpErrors = (dispatch, response) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestDifferentialExpression = (set1, set2, num_genes = 50) => async (
|
const requestDifferentialExpression = (
|
||||||
dispatch,
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
getState
|
set1: any,
|
||||||
) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
set2: any,
|
||||||
|
num_genes = 50
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
dispatch({ type: "request differential expression started" });
|
dispatch({ type: "request differential expression started" });
|
||||||
try {
|
try {
|
||||||
/*
|
/*
|
||||||
@@ -210,7 +243,9 @@ const requestDifferentialExpression = (set1, set2, num_genes = 50) => async (
|
|||||||
const varIndex = await annoMatrix.fetch("var", varIndexName);
|
const varIndex = await annoMatrix.fetch("var", varIndexName);
|
||||||
const diffexpLists = { negative: [], positive: [] };
|
const diffexpLists = { negative: [], positive: [] };
|
||||||
for (const polarity of Object.keys(diffexpLists)) {
|
for (const polarity of Object.keys(diffexpLists)) {
|
||||||
diffexpLists[polarity] = response[polarity].map((v) => [
|
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
diffexpLists[polarity] = response[polarity].map((v: any) => [
|
||||||
varIndex.at(v[0], varIndexName),
|
varIndex.at(v[0], varIndexName),
|
||||||
...v.slice(1),
|
...v.slice(1),
|
||||||
]);
|
]);
|
||||||
@@ -229,7 +264,8 @@ const requestDifferentialExpression = (set1, set2, num_genes = 50) => async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function fetchJson(pathAndQuery) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
function fetchJson(pathAndQuery: any) {
|
||||||
return doJsonRequest(
|
return doJsonRequest(
|
||||||
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
|
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
|
||||||
);
|
);
|
||||||
@@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
Action creators for selection
|
||||||
|
*/
|
||||||
|
export const selectContinuousMetadataAction = (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
type: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
query: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
range: any,
|
||||||
|
oldProps = {} // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
|
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||||
|
|
||||||
|
const selection = range
|
||||||
|
? {
|
||||||
|
mode: "range",
|
||||||
|
lo: range[0],
|
||||||
|
hi: range[1],
|
||||||
|
inclusive: true, // [lo, hi] incluisve selection
|
||||||
|
}
|
||||||
|
: { mode: "all" };
|
||||||
|
|
||||||
|
const obsCrossfilter = await prevObsCrossfilter.select(...query, selection);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type,
|
||||||
|
obsCrossfilter,
|
||||||
|
range,
|
||||||
|
...oldProps,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectCategoricalMetadataAction = (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
type: any, // action type
|
||||||
|
// annotation category name
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
metadataField: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
labels: any,
|
||||||
|
// the label being selected/deselected
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
label: any,
|
||||||
|
// bool
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
isSelected: any,
|
||||||
|
oldProps = {}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
|
const {
|
||||||
|
obsCrossfilter: prevObsCrossfilter,
|
||||||
|
categoricalSelection,
|
||||||
|
} = getState();
|
||||||
|
|
||||||
|
const labelSelectionState = new Map(categoricalSelection[metadataField]);
|
||||||
|
labels.forEach(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
(l: any) => labelSelectionState.has(l) || labelSelectionState.set(l, true)
|
||||||
|
);
|
||||||
|
labelSelectionState.set(label, isSelected);
|
||||||
|
|
||||||
|
const values = Array.from(labelSelectionState.keys()).filter((k) =>
|
||||||
|
labelSelectionState.get(k)
|
||||||
|
);
|
||||||
|
const selection = {
|
||||||
|
mode: "exact",
|
||||||
|
values,
|
||||||
|
};
|
||||||
|
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||||
|
"obs",
|
||||||
|
metadataField,
|
||||||
|
selection
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type,
|
||||||
|
obsCrossfilter,
|
||||||
|
metadataField,
|
||||||
|
labelSelectionState,
|
||||||
|
...oldProps,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectCategoricalAllMetadataAction = (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
type: any, // action type
|
||||||
|
// annotation category name
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
metadataField: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
labels: any,
|
||||||
|
// bool, select all or none
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
isSelected: any,
|
||||||
|
oldProps = {}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
|
const {
|
||||||
|
obsCrossfilter: prevObsCrossfilter,
|
||||||
|
categoricalSelection,
|
||||||
|
} = getState();
|
||||||
|
|
||||||
|
const labelSelectionState = new Map(categoricalSelection[metadataField]);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
labels.forEach((label: any) => labelSelectionState.set(label, isSelected));
|
||||||
|
|
||||||
|
const selection = { mode: isSelected ? "all" : "none" };
|
||||||
|
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||||
|
"obs",
|
||||||
|
metadataField,
|
||||||
|
selection
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type,
|
||||||
|
obsCrossfilter,
|
||||||
|
metadataField,
|
||||||
|
labelSelectionState,
|
||||||
|
...oldProps,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Graph selection-related actions
|
||||||
|
**/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphBrushStartAction = () =>
|
||||||
|
/* no change to crossfilter until a change fires */
|
||||||
|
({ type: "graph brush start" });
|
||||||
|
|
||||||
|
const _graphBrushWithinRectAction = (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
type: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
embName: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
brushCoords: any
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
) => async (dispatch: any, getState: any) => {
|
||||||
|
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||||
|
|
||||||
|
const selection = { mode: "within-rect", ...brushCoords };
|
||||||
|
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||||
|
"emb",
|
||||||
|
embName,
|
||||||
|
selection
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type,
|
||||||
|
obsCrossfilter,
|
||||||
|
brushCoords,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
const _graphAllAction = (type: any, embName: any) => async (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
|
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||||
|
|
||||||
|
const obsCrossfilter = await prevObsCrossfilter.select("emb", embName, {
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type,
|
||||||
|
obsCrossfilter,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphBrushChangeAction = (embName: any, brushCoords: any) =>
|
||||||
|
_graphBrushWithinRectAction("graph brush change", embName, brushCoords);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphBrushEndAction = (embName: any, brushCoords: any) =>
|
||||||
|
_graphBrushWithinRectAction("graph brush end", embName, brushCoords);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphBrushCancelAction = (embName: any) =>
|
||||||
|
_graphAllAction("graph brush cancel", embName);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphBrushDeselectAction = (embName: any) =>
|
||||||
|
_graphAllAction("graph brush deselect", embName);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphLassoStartAction = () =>
|
||||||
|
/* no change to crossfilter until a change fires */
|
||||||
|
({ type: "graph lasso start" });
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphLassoCancelAction = (embName: any) =>
|
||||||
|
_graphAllAction("graph lasso cancel", embName);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphLassoDeselectAction = (embName: any) =>
|
||||||
|
_graphAllAction("graph lasso cancel", embName);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const graphLassoEndAction = (embName: any, polygon: any) => async (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
|
const { obsCrossfilter: prevObsCrossfilter } = getState();
|
||||||
|
|
||||||
|
const selection = {
|
||||||
|
mode: "within-polygon",
|
||||||
|
polygon,
|
||||||
|
};
|
||||||
|
const obsCrossfilter = await prevObsCrossfilter.select(
|
||||||
|
"emb",
|
||||||
|
embName,
|
||||||
|
selection
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "graph lasso end",
|
||||||
|
obsCrossfilter,
|
||||||
|
polygon,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Differential expression set selection
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const setCellSetFromSelection = (cellSetId: any) => (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
|
const { obsCrossfilter } = getState();
|
||||||
|
const selected = obsCrossfilter.allSelectedLabels();
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: `store current cell selection as differential set ${cellSetId}`,
|
||||||
|
data: selected.length > 0 ? selected : null,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -18,7 +18,13 @@ import {
|
|||||||
_userResetSubsetAnnoMatrix,
|
_userResetSubsetAnnoMatrix,
|
||||||
} from "../util/stateManager/viewStackHelpers";
|
} from "../util/stateManager/viewStackHelpers";
|
||||||
|
|
||||||
export const clipAction = (min, max) => (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const clipAction = (min: any, max: any) => (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
dispatch: any,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
getState: any
|
||||||
|
) => {
|
||||||
/*
|
/*
|
||||||
apply a clip to the current annoMatrix. By convention, the clip
|
apply a clip to the current annoMatrix. By convention, the clip
|
||||||
view is ALWAYS the top view.
|
view is ALWAYS the top view.
|
||||||
@@ -34,7 +40,8 @@ export const clipAction = (min, max) => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subsetAction = () => (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const subsetAction = () => (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
Subset the annoMatrix to the current crossfilter selection by pushing a
|
Subset the annoMatrix to the current crossfilter selection by pushing a
|
||||||
subset view.
|
subset view.
|
||||||
@@ -58,7 +65,8 @@ export const subsetAction = () => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resetSubsetAction = () => (dispatch, getState) => {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
export const resetSubsetAction = () => (dispatch: any, getState: any) => {
|
||||||
/*
|
/*
|
||||||
Reset the annoMatrix to all data. Because we may have multiple views
|
Reset the annoMatrix to all data. Because we may have multiple views
|
||||||
stacked, we pop them all. By convention, any clip transformation will
|
stacked, we pop them all. By convention, any clip transformation will
|
||||||
@@ -17,6 +17,39 @@ import { _queryValidate, _queryCacheKey } from "./query";
|
|||||||
const _dataframeCache = dataframeMemo(128);
|
const _dataframeCache = dataframeMemo(128);
|
||||||
|
|
||||||
export default class AnnoMatrix {
|
export default class AnnoMatrix {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
public isView: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
public nObs: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
public nVar: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
public rowIndex: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
public schema: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
public userFlags: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
public viewOf: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
protected _cache: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
private _pendingLoad: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
private _whereCache: any;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
|
||||||
|
private _gcInfo: any;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Abstract base class for all AnnoMatrix objects. This class provides a proxy
|
Abstract base class for all AnnoMatrix objects. This class provides a proxy
|
||||||
to the annotated matrix data authoritatively served by the server/back-end.
|
to the annotated matrix data authoritatively served by the server/back-end.
|
||||||
@@ -47,6 +80,7 @@ export default class AnnoMatrix {
|
|||||||
subset(annoMatrix, rowLabels) -> annoMatrix
|
subset(annoMatrix, rowLabels) -> annoMatrix
|
||||||
etc.
|
etc.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
static fields() {
|
static fields() {
|
||||||
/*
|
/*
|
||||||
return the fields present in the AnnoMatrix instance.
|
return the fields present in the AnnoMatrix instance.
|
||||||
@@ -54,7 +88,8 @@ export default class AnnoMatrix {
|
|||||||
return ["obs", "var", "emb", "X"];
|
return ["obs", "var", "emb", "X"];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(schema, nObs, nVar, rowIndex = null) {
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
|
||||||
|
constructor(schema: any, nObs: any, nVar: any, rowIndex = null) {
|
||||||
/*
|
/*
|
||||||
Private constructor - this is an abstract base class. Do not use.
|
Private constructor - this is an abstract base class. Do not use.
|
||||||
*/
|
*/
|
||||||
@@ -83,13 +118,13 @@ export default class AnnoMatrix {
|
|||||||
this.userFlags = {};
|
this.userFlags = {};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Private instance variables.
|
Private instance variables.
|
||||||
|
|
||||||
These are caches - lazily loaded. The only guarantee is that if they
|
These are caches - lazily loaded. The only guarantee is that if they
|
||||||
are loaded, they will conform to the schema & dimensionality constraints.
|
are loaded, they will conform to the schema & dimensionality constraints.
|
||||||
|
|
||||||
Do NOT use directly - instead, use the fetch() and preload() API.
|
Do NOT use directly - instead, use the fetch() and preload() API.
|
||||||
*/
|
*/
|
||||||
this._cache = {
|
this._cache = {
|
||||||
obs: Dataframe.empty(this.rowIndex),
|
obs: Dataframe.empty(this.rowIndex),
|
||||||
var: Dataframe.empty(this.rowIndex),
|
var: Dataframe.empty(this.rowIndex),
|
||||||
@@ -109,6 +144,8 @@ export default class AnnoMatrix {
|
|||||||
/**
|
/**
|
||||||
** Schema helper/accessors
|
** Schema helper/accessors
|
||||||
**/
|
**/
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
getMatrixColumns(field) {
|
getMatrixColumns(field) {
|
||||||
/*
|
/*
|
||||||
Return array of column names in the field. ONLY supported on the
|
Return array of column names in the field. ONLY supported on the
|
||||||
@@ -121,7 +158,7 @@ export default class AnnoMatrix {
|
|||||||
return _schemaColumns(this.schema, field);
|
return _schemaColumns(this.schema, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this -- need to be able to call this on instances
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types -- need to be able to call this on instances
|
||||||
getMatrixFields() {
|
getMatrixFields() {
|
||||||
/*
|
/*
|
||||||
Return array of fields in this annoMatrix. Currently hard-wired to
|
Return array of fields in this annoMatrix. Currently hard-wired to
|
||||||
@@ -132,6 +169,8 @@ export default class AnnoMatrix {
|
|||||||
return AnnoMatrix.fields();
|
return AnnoMatrix.fields();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
getColumnSchema(field, col) {
|
getColumnSchema(field, col) {
|
||||||
/*
|
/*
|
||||||
Return the schema for the field & column ,eg,
|
Return the schema for the field & column ,eg,
|
||||||
@@ -144,6 +183,8 @@ export default class AnnoMatrix {
|
|||||||
return _getColumnSchema(this.schema, field, col);
|
return _getColumnSchema(this.schema, field, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
getColumnDimensions(field, col) {
|
getColumnDimensions(field, col) {
|
||||||
/*
|
/*
|
||||||
Return the dimensions on this field / column. For most fields, which are 1D,
|
Return the dimensions on this field / column. For most fields, which are 1D,
|
||||||
@@ -162,10 +203,12 @@ export default class AnnoMatrix {
|
|||||||
/**
|
/**
|
||||||
** General utility methods
|
** General utility methods
|
||||||
**/
|
**/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
base() {
|
base() {
|
||||||
/*
|
/*
|
||||||
return the base of view, or `this` if not a view.
|
return the base of view, or `this` if not a view.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias --- FIXME: disabled temporarily on migrate to TS.
|
||||||
let annoMatrix = this;
|
let annoMatrix = this;
|
||||||
while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf;
|
while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf;
|
||||||
return annoMatrix;
|
return annoMatrix;
|
||||||
@@ -174,6 +217,8 @@ export default class AnnoMatrix {
|
|||||||
/**
|
/**
|
||||||
** Load / read interfaces
|
** Load / read interfaces
|
||||||
**/
|
**/
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
fetch(field, q) {
|
fetch(field, q) {
|
||||||
/*
|
/*
|
||||||
Return the given query on a single matrix field as a single dataframe.
|
Return the given query on a single matrix field as a single dataframe.
|
||||||
@@ -231,6 +276,8 @@ export default class AnnoMatrix {
|
|||||||
return this._fetch(field, q);
|
return this._fetch(field, q);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
prefetch(field, q) {
|
prefetch(field, q) {
|
||||||
/*
|
/*
|
||||||
Start a data fetch & cache fill. Identical to fetch() except it does
|
Start a data fetch & cache fill. Identical to fetch() except it does
|
||||||
@@ -261,7 +308,8 @@ export default class AnnoMatrix {
|
|||||||
** The actual implementation is in the sub-classes, which MUST override these.
|
** The actual implementation is in the sub-classes, which MUST override these.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
addObsAnnoCategory(col, category) {
|
addObsAnnoCategory(col, category) {
|
||||||
/*
|
/*
|
||||||
Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix.
|
Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix.
|
||||||
@@ -278,7 +326,8 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
async removeObsAnnoCategory(col, category, unassignedCategory) {
|
async removeObsAnnoCategory(col, category, unassignedCategory) {
|
||||||
/*
|
/*
|
||||||
Remove a category value from an obs column, reassign any obs having that value
|
Remove a category value from an obs column, reassign any obs having that value
|
||||||
@@ -299,7 +348,8 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
dropObsColumn(col) {
|
dropObsColumn(col) {
|
||||||
/*
|
/*
|
||||||
Drop an entire writable column, eg a user-created obs annotation. Typical use
|
Drop an entire writable column, eg a user-created obs annotation. Typical use
|
||||||
@@ -315,7 +365,8 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'colSchema' is declared but its value is never rea... Remove this comment to see the full error message
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
addObsColumn(colSchema, Ctor, value) {
|
addObsColumn(colSchema, Ctor, value) {
|
||||||
/*
|
/*
|
||||||
Add a new writable OBS annotation column, with the caller-specified schema, initial value
|
Add a new writable OBS annotation column, with the caller-specified schema, initial value
|
||||||
@@ -344,7 +395,8 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'oldCol' is declared but its value is never read.
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
renameObsColumn(oldCol, newCol) {
|
renameObsColumn(oldCol, newCol) {
|
||||||
/*
|
/*
|
||||||
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
|
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
|
||||||
@@ -359,7 +411,8 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
async setObsColumnValues(col, obsLabels, value) {
|
async setObsColumnValues(col, obsLabels, value) {
|
||||||
/*
|
/*
|
||||||
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
|
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
|
||||||
@@ -377,7 +430,8 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read.
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
async resetObsColumnValues(col, oldValue, newValue) {
|
async resetObsColumnValues(col, oldValue, newValue) {
|
||||||
/*
|
/*
|
||||||
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
|
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
|
||||||
@@ -394,7 +448,8 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
|
// @ts-expect-error ts-migrate(6133) FIXME: 'colSchema' is declared but its value is never rea... Remove this comment to see the full error message
|
||||||
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
addEmbedding(colSchema) {
|
addEmbedding(colSchema) {
|
||||||
/*
|
/*
|
||||||
Add a new obs embedding to the AnnoMatrix, with provided schema.
|
Add a new obs embedding to the AnnoMatrix, with provided schema.
|
||||||
@@ -407,28 +462,38 @@ export default class AnnoMatrix {
|
|||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
getCacheKeys(field, query) {
|
getCacheKeys(field, query) {
|
||||||
/*
|
/*
|
||||||
Return cache keys for columns associated with this query. May return
|
Return cache keys for columns associated with this query. May return
|
||||||
[unknown] if no keys are known (ie, nothing is or was cached).
|
[unknown] if no keys are known (ie, nothing is or was cached).
|
||||||
*/
|
*/
|
||||||
return _whereCacheGet(this._whereCache, this.schema, field, query);
|
return _whereCacheGet(this._whereCache, this.schema, field, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Private interfaces below.
|
** Private interfaces below.
|
||||||
**/
|
**/
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
_resolveCachedQueries(field, queries) {
|
_resolveCachedQueries(field, queries) {
|
||||||
return queries
|
return (
|
||||||
.map((query) =>
|
queries
|
||||||
_whereCacheGet(this._whereCache, this.schema, field, query).filter(
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'query' implicitly has an 'any' type.
|
||||||
(cacheKey) =>
|
.map((query) =>
|
||||||
cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
|
_whereCacheGet(this._whereCache, this.schema, field, query).filter(
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'cacheKey' implicitly has an 'any' type.
|
||||||
|
(cacheKey) =>
|
||||||
|
cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
.flat()
|
||||||
.flat();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
async _fetch(field, q) {
|
async _fetch(field, q) {
|
||||||
if (!AnnoMatrix.fields().includes(field)) return undefined;
|
if (!AnnoMatrix.fields().includes(field)) return undefined;
|
||||||
const queries = Array.isArray(q) ? q : [q];
|
const queries = Array.isArray(q) ? q : [q];
|
||||||
@@ -441,6 +506,7 @@ export default class AnnoMatrix {
|
|||||||
/* find any query not already cached */
|
/* find any query not already cached */
|
||||||
const uncachedQueries = queries.filter((query) =>
|
const uncachedQueries = queries.filter((query) =>
|
||||||
_whereCacheGet(this._whereCache, this.schema, field, query).some(
|
_whereCacheGet(this._whereCache, this.schema, field, query).some(
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'cacheKey' implicitly has an 'any' type.
|
||||||
(cacheKey) =>
|
(cacheKey) =>
|
||||||
cacheKey === undefined || !this._cache[field].hasCol(cacheKey)
|
cacheKey === undefined || !this._cache[field].hasCol(cacheKey)
|
||||||
)
|
)
|
||||||
@@ -450,8 +516,10 @@ export default class AnnoMatrix {
|
|||||||
if (uncachedQueries.length > 0) {
|
if (uncachedQueries.length > 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
uncachedQueries.map((query) =>
|
uncachedQueries.map((query) =>
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter '_field' implicitly has an 'any' type.
|
||||||
this._getPendingLoad(field, query, async (_field, _query) => {
|
this._getPendingLoad(field, query, async (_field, _query) => {
|
||||||
/* fetch, then index. _doLoad is subclass interface */
|
/* fetch, then index. _doLoad is subclass interface */
|
||||||
|
// @ts-expect-error ts-migrate(2488) FIXME: Type 'void' must have a '[Symbol.iterator]()' meth... Remove this comment to see the full error message
|
||||||
const [whereCacheUpdate, df] = await this._doLoad(_field, _query);
|
const [whereCacheUpdate, df] = await this._doLoad(_field, _query);
|
||||||
this._cache[_field] = this._cache[_field].withColsFrom(df);
|
this._cache[_field] = this._cache[_field].withColsFrom(df);
|
||||||
this._whereCache = _whereCacheMerge(
|
this._whereCache = _whereCacheMerge(
|
||||||
@@ -472,6 +540,8 @@ export default class AnnoMatrix {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
async _getPendingLoad(field, query, fetchFn) {
|
async _getPendingLoad(field, query, fetchFn) {
|
||||||
/*
|
/*
|
||||||
Given a query on a field, ensure that we only have a single outstanding
|
Given a query on a field, ensure that we only have a single outstanding
|
||||||
@@ -493,7 +563,7 @@ export default class AnnoMatrix {
|
|||||||
return this._pendingLoad[field][key];
|
return this._pendingLoad[field][key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this -- make sure subclass implements
|
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements
|
||||||
async _doLoad() {
|
async _doLoad() {
|
||||||
_subclassResponsibility();
|
_subclassResponsibility();
|
||||||
}
|
}
|
||||||
@@ -527,19 +597,22 @@ export default class AnnoMatrix {
|
|||||||
To be effective, the GC callback needs to be invoked from the undo/redo code,
|
To be effective, the GC callback needs to be invoked from the undo/redo code,
|
||||||
as much of the cache is pinned by that data structure.
|
as much of the cache is pinned by that data structure.
|
||||||
*/
|
*/
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
_gcField(field, isHot, pinnedColumns) {
|
_gcField(field, isHot, pinnedColumns) {
|
||||||
const maxColumns = isHot ? 256 : 10; // maybe to aggressive?
|
const maxColumns = isHot ? 256 : 10;
|
||||||
|
|
||||||
const cache = this._cache[field];
|
const cache = this._cache[field];
|
||||||
if (cache.colIndex.size() < maxColumns) return; // trivial rejection
|
if (cache.colIndex.size() < maxColumns) return; // trivial rejection
|
||||||
|
|
||||||
const candidates = cache.colIndex
|
const candidates = cache.colIndex
|
||||||
.labels()
|
.labels()
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type.
|
||||||
.filter((col) => !pinnedColumns.includes(col));
|
.filter((col) => !pinnedColumns.includes(col));
|
||||||
|
|
||||||
const excessCount = candidates.length + pinnedColumns.length - maxColumns;
|
const excessCount = candidates.length + pinnedColumns.length - maxColumns;
|
||||||
if (excessCount > 0) {
|
if (excessCount > 0) {
|
||||||
const { _gcInfo } = this;
|
const { _gcInfo } = this;
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'a' implicitly has an 'any' type.
|
||||||
candidates.sort((a, b) => {
|
candidates.sort((a, b) => {
|
||||||
let atime = _gcInfo.get(_columnCacheKey(field, a));
|
let atime = _gcInfo.get(_columnCacheKey(field, a));
|
||||||
if (atime === undefined) atime = 0;
|
if (atime === undefined) atime = 0;
|
||||||
@@ -558,13 +631,17 @@ export default class AnnoMatrix {
|
|||||||
// )}]`
|
// )}]`
|
||||||
// );
|
// );
|
||||||
this._cache[field] = toDrop.reduce(
|
this._cache[field] = toDrop.reduce(
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'df' implicitly has an 'any' type.
|
||||||
(df, col) => df.dropCol(col),
|
(df, col) => df.dropCol(col),
|
||||||
this._cache[field]
|
this._cache[field]
|
||||||
);
|
);
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type.
|
||||||
toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col)));
|
toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
_gcFetchCleanup(field, pinnedColumns) {
|
_gcFetchCleanup(field, pinnedColumns) {
|
||||||
/*
|
/*
|
||||||
Called during data load/fetch. By definition, this is 'hot', so we
|
Called during data load/fetch. By definition, this is 'hot', so we
|
||||||
@@ -579,6 +656,8 @@ export default class AnnoMatrix {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'hints' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
_gc(hints) {
|
_gc(hints) {
|
||||||
/*
|
/*
|
||||||
Called from middleware, or elsewhere. isHot is true if we are in the active store,
|
Called from middleware, or elsewhere. isHot is true if we are in the active store,
|
||||||
@@ -591,6 +670,8 @@ export default class AnnoMatrix {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
_gcUpdateStats(field, dataframe) {
|
_gcUpdateStats(field, dataframe) {
|
||||||
/*
|
/*
|
||||||
called each time a query is performed, allowing the gc to update any bookkeeping
|
called each time a query is performed, allowing the gc to update any bookkeeping
|
||||||
@@ -600,6 +681,7 @@ export default class AnnoMatrix {
|
|||||||
const cols = dataframe.colIndex.labels();
|
const cols = dataframe.colIndex.labels();
|
||||||
const { _gcInfo } = this;
|
const { _gcInfo } = this;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'c' implicitly has an 'any' type.
|
||||||
cols.forEach((c) => {
|
cols.forEach((c) => {
|
||||||
_gcInfo.set(_columnCacheKey(field, c), now);
|
_gcInfo.set(_columnCacheKey(field, c), now);
|
||||||
});
|
});
|
||||||
@@ -617,6 +699,8 @@ export default class AnnoMatrix {
|
|||||||
|
|
||||||
Do not override _clone();
|
Do not override _clone();
|
||||||
**/
|
**/
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'clone' implicitly has an 'any' type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
_cloneDeeper(clone) {
|
_cloneDeeper(clone) {
|
||||||
clone._cache = _shallowClone(this._cache);
|
clone._cache = _shallowClone(this._cache);
|
||||||
clone._gcInfo = new Map();
|
clone._gcInfo = new Map();
|
||||||
@@ -629,6 +713,7 @@ export default class AnnoMatrix {
|
|||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
|
||||||
_clone() {
|
_clone() {
|
||||||
const clone = _shallowClone(this);
|
const clone = _shallowClone(this);
|
||||||
this._cloneDeeper(clone);
|
this._cloneDeeper(clone);
|
||||||
@@ -640,6 +725,7 @@ export default class AnnoMatrix {
|
|||||||
/*
|
/*
|
||||||
private utility functions below
|
private utility functions below
|
||||||
*/
|
*/
|
||||||
|
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type.
|
||||||
function _columnCacheKey(field, column) {
|
function _columnCacheKey(field, column) {
|
||||||
return `${field}/${column}`;
|
return `${field}/${column}`;
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user