TS Revert (1) (#2402)

* revert all commits to before Typescript migration

* update compat workflow to match latest deps (#2335)

* update compat workflow to match latest deps

* attempt to debug

* attempt to debug

* remove debugging code

* typo

* update deps to match desktop (#2340)

* fix: don't run lint with `--fix` on push tests (#2273)

* fix: don't run lint with `--fix` on push tests

* npx

Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>

* rename X_approx_distribution to X_approximate_distribution (#2337)

* Correctly handle non-finite numbers in heuristic determination of X distribution (#2342)

* handle non-finites explicitly

* improve and test edge case handling for distribution estimation

* revert debugging changes

* code readability

* clean up type inferencing (#2332)

* unit tests for 64 bit conversion

* clean up type handling

* type inference tests

* more type inference fixes

* use schema to determine user intent for data typing

* stop using deprecated API

* fbs type encoding test

* add missing test

* add more tests

* correctly infer X type for CXG adaptor

* lint

* fix typo

* ts migration

* cleanup from PR review

* lint

* PR review changes

* remove unused packages from client (#2359)

* remove unused packages from client

* add missing peer dep

* fix: disable FE auth testing on compatibility tests (#2377)

* update: release process (#2277)

Co-authored-by: maniarathi <mani.arathi@gmail.com>

* fix: remove spaces in param setup (#2380)

* delete deploy workflow (#2396)

* undo reformatting which now does not pass lint

* fix snapshots which changed due to npm dep changes

* add missing quoting to snapshot

* another snapshot typo fix

* TS Revert (2) - replay PR #2347 and #2354 (#2403)

* replay edits from PR 2347

* TS Revert (3) - replay edits in PR #2327 (#2404)

* replay edits in PR 2327

* TS Revert (4) - replay PR #2355 (#2405)

* replay edits in PR 2355

* add additional babel config

* reformat with new prettier config

Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
This commit is contained in:
Bruce Martin
2021-08-23 15:01:36 -07:00
committed by GitHub
co-authored by maniarathi Madison Dunitz Severiano Badajoz
parent 295590a7c6
commit eaae6df5e3
253 changed files with 11175 additions and 22271 deletions
+1 -1
View File
@@ -18,4 +18,4 @@ geneset_to_delete,,,
geneset_to_edit,,, geneset_to_edit,,,
fill_this_geneset,,, fill_this_geneset,,,
empty_this_geneset,,SIK1, empty_this_geneset,,SIK1,
brush_this_gene,,SIK1, brush_this_gene,,SIK1,
1 # Test fixture
18 geneset_to_edit,,,
19 fill_this_geneset,,,
20 empty_this_geneset,,SIK1,
21 brush_this_gene,,SIK1,
+506
View File
@@ -0,0 +1,506 @@
/* 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) => page.evaluate((elem) => 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) => 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) => 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) => 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 */
-597
View File
@@ -1,597 +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";
// 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`);
}
export async function clip(min = "0", max = "100"): Promise<void> {
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,12 +58,10 @@ 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])
); );
} }
@@ -161,12 +159,10 @@ 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])
); );
} }
@@ -258,7 +254,6 @@ 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
); );
} }
@@ -280,7 +275,6 @@ 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`
@@ -82,8 +82,7 @@ const genesetDescriptionID =
const genesetDescriptionString = "fourth_gene_set: fourth description"; const genesetDescriptionString = "fourth_gene_set: fourth description";
const genesetToCheckForDescription = "fourth_gene_set"; 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) {
async function setup(config: any) {
await goToPage(appUrlBase); await goToPage(appUrlBase);
if (config.categoricalAnno) { if (config.categoricalAnno) {
@@ -157,8 +156,7 @@ 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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (selector) => !document.querySelector(selector),
(selector: any) => !document.querySelector(selector),
{}, {},
getTestClass("gene-loading-spinner") getTestClass("gene-loading-spinner")
); );
@@ -173,8 +171,7 @@ 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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (selector) => !document.querySelector(selector),
(selector: any) => !document.querySelector(selector),
{}, {},
getTestClass("gene-loading-spinner") getTestClass("gene-loading-spinner")
); );
@@ -405,13 +402,8 @@ describe.each([
expect(actualLabelName).toBe(expectedLabelName); expect(actualLabelName).toBe(expectedLabelName);
expect(actualLabelCount).toBe(expectedLabelCount); expect(actualLabelCount).toBe(expectedLabelCount);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function getInnerText(element, className) {
async function getInnerText(element: any, className: any) { return element.$eval(getTestClass(className), (node) => node?.innerText);
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
);
} }
}); });
@@ -436,9 +428,7 @@ 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]
); );
}); });
@@ -498,7 +488,6 @@ 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);
@@ -508,12 +497,10 @@ 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);
}); });
@@ -544,9 +531,7 @@ 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) => page.evaluate((element) => element.outerHTML, label))
page.evaluate((element) => element.outerHTML, label)
)
); );
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
@@ -574,11 +559,9 @@ describe.each([
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function assertCategoryExists(categoryName) {
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")
); );
@@ -586,8 +569,7 @@ describe.each([
return expect(result).toBe(categoryName); return expect(result).toBe(categoryName);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function assertLabelExists(categoryName, labelName) {
async function assertLabelExists(categoryName: any, labelName: any) {
await expect(page).toMatchElement( await expect(page).toMatchElement(
getTestId(`${categoryName}:category-expand`) getTestId(`${categoryName}:category-expand`)
); );
@@ -599,13 +581,11 @@ 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);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function assertLabelDoesNotExist(categoryName, labelName) {
async function assertLabelDoesNotExist(categoryName: any, labelName: any) {
await expandCategory(categoryName); await expandCategory(categoryName);
const result = await page.$( const result = await page.$(
`[data-testid='categorical-value-${categoryName}-${labelName}']` `[data-testid='categorical-value-${categoryName}-${labelName}']`
+4 -4
View File
@@ -1,10 +1,10 @@
{ {
"testRunner": "jest-circus/runner", "testRunner": "jest-circus/runner",
"preset": "jest-puppeteer", "preset": "jest-puppeteer",
"testMatch": ["**/__tests__/**/?(*.)(spec|test).ts?(x)"], "testMatch": ["**/__tests__/**/?(*.)(spec|test).js?(x)"],
"setupFiles": ["../setupMissingGlobals.ts"], "setupFiles": ["../setupMissingGlobals.js"],
"setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.ts"], "setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.js"],
"globalSetup": "../globalSetup.ts", "globalSetup": "../globalSetup.js",
"globalTeardown": "jest-environment-puppeteer/teardown", "globalTeardown": "jest-environment-puppeteer/teardown",
"testEnvironment": "./screenshot_env.js" "testEnvironment": "./screenshot_env.js"
} }
@@ -23,7 +23,6 @@ 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) => {
@@ -50,8 +49,7 @@ 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?
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. msg.args().map((arg) => arg._remoteObject.description)
msg.args().map((arg: any) => arg._remoteObject.description)
); );
throw new Error(`Console error: ${errorMsgText}`); throw new Error(`Console error: ${errorMsgText}`);
} }
+131
View File
@@ -0,0 +1,131 @@
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
export function getTestId(id) {
return `[data-testid='${id}']`;
}
export function getTestClass(className) {
return `[data-testclass='${className}']`;
}
export async function waitByID(testId, props = {}) {
return page.waitForSelector(getTestId(testId), props);
}
export async function waitByClass(testClass, props = {}) {
return page.waitForSelector(`[data-testclass='${testClass}']`, props);
}
export async function waitForAllByIds(testIds) {
await Promise.all(
testIds.map((testId) => page.waitForSelector(getTestId(testId)))
);
}
export async function getAllByClass(testClass) {
return page.$$(`[data-testclass=${testClass}]`);
}
export async function typeInto(testId, text) {
// blueprint's typeahead is treating typing weird, clicking & waiting first solves this
// only works for text without special characters
await waitByID(testId);
const selector = getTestId(testId);
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
await page.type(selector, text);
}
export async function clearInputAndTypeInto(testId, text) {
await waitByID(testId);
const selector = getTestId(testId);
// only works for text without special characters
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
// select all
await page.click(selector, { clickCount: 3 });
await page.keyboard.press("Backspace");
await page.type(selector, text);
}
export async function clickOn(testId, options = {}) {
await expect(page).toClick(getTestId(testId), options);
}
/**
* (thuang): There are times when Puppeteer clicks on a button and the page doesn't respond.
* So I added clickOnUntil() to retry clicking until a given condition is met.
*/
export async function clickOnUntil(testId, assert) {
const MAX_RETRY = 10;
const WAIT_FOR_MS = 200;
let retry = 0;
while (retry < MAX_RETRY) {
try {
await clickOn(testId);
await assert();
break;
} catch (error) {
retry += 1;
await page.waitForTimeout(WAIT_FOR_MS);
}
}
if (retry === MAX_RETRY) {
throw Error("clickOnUntil() assertion failed!");
}
}
export async function getOneElementInnerHTML(selector, options = {}) {
await page.waitForSelector(selector, options);
return page.$eval(selector, (el) => el.innerHTML);
}
export async function getOneElementInnerText(selector) {
expect(page).toMatchElement(selector);
return page.$eval(selector, (el) => el.innerText);
}
export async function getElementCoordinates(testId) {
return page.$eval(getTestId(testId), (elem) => {
const { left, top } = elem.getBoundingClientRect();
return [left, top];
});
}
async function clickTermsOfService() {
if (!(await isElementPresent(getTestId("tos-cookies-accept")))) return;
await clickOn("tos-cookies-accept");
}
async function nameNewAnnotation() {
if (await isElementPresent(getTestId("annotation-dialog"))) {
await typeInto("new-annotation-name", "ignoreE2E");
await clickOn("submit-annotation");
// wait for the page to load
await waitByClass("autosave-complete");
}
}
export async function goToPage(url) {
await page.goto(url, {
waitUntil: "networkidle0",
});
await nameNewAnnotation();
await clickTermsOfService();
}
export async function isElementPresent(selector, options) {
return Boolean(await page.$(selector, options));
}
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
-151
View File
@@ -1,151 +0,0 @@
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export function getTestId(id: any) {
return `[data-testid='${id}']`;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export function getTestClass(className: any) {
return `[data-testclass='${className}']`;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function waitByID(testId: any, props = {}) {
return page.waitForSelector(getTestId(testId), props);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function waitByClass(testClass: any, props = {}) {
return page.waitForSelector(`[data-testclass='${testClass}']`, props);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function waitForAllByIds(testIds: any) {
await Promise.all(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
testIds.map((testId: any) => page.waitForSelector(getTestId(testId)))
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getAllByClass(testClass: any) {
return page.$$(`[data-testclass=${testClass}]`);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function typeInto(testId: any, text: any) {
// blueprint's typeahead is treating typing weird, clicking & waiting first solves this
// only works for text without special characters
await waitByID(testId);
const selector = getTestId(testId);
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
await page.type(selector, text);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function clearInputAndTypeInto(testId: any, text: any) {
await waitByID(testId);
const selector = getTestId(testId);
// only works for text without special characters
// type ahead can be annoying if you don't pause before you type
await page.click(selector);
await page.waitForTimeout(200);
// select all
await page.click(selector, { clickCount: 3 });
await page.keyboard.press("Backspace");
await page.type(selector, text);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function clickOn(testId: any, options = {}) {
await expect(page).toClick(getTestId(testId), options);
}
/**
* (thuang): There are times when Puppeteer clicks on a button and the page doesn't respond.
* So I added clickOnUntil() to retry clicking until a given condition is met.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function clickOnUntil(testId: any, assert: any) {
const MAX_RETRY = 10;
const WAIT_FOR_MS = 200;
let retry = 0;
while (retry < MAX_RETRY) {
try {
await clickOn(testId);
await assert();
break;
} catch (error) {
retry += 1;
await page.waitForTimeout(WAIT_FOR_MS);
}
}
if (retry === MAX_RETRY) {
throw Error("clickOnUntil() assertion failed!");
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getOneElementInnerHTML(selector: any, options = {}) {
await page.waitForSelector(selector, options);
return page.$eval(selector, (el) => el.innerHTML);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getOneElementInnerText(selector: any) {
expect(page).toMatchElement(selector);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return page.$eval(selector, (el) => (el as any).innerText);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function getElementCoordinates(testId: any) {
return page.$eval(getTestId(testId), (elem) => {
const { left, top } = elem.getBoundingClientRect();
return [left, top];
});
}
async function clickTermsOfService() {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
if (!(await isElementPresent(getTestId("tos-cookies-accept")))) return;
await clickOn("tos-cookies-accept");
}
async function nameNewAnnotation() {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
if (await isElementPresent(getTestId("annotation-dialog"))) {
await typeInto("new-annotation-name", "ignoreE2E");
await clickOn("submit-annotation");
// wait for the page to load
await waitByClass("autosave-complete");
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function goToPage(url: any) {
await page.goto(url, {
waitUntil: "networkidle0",
});
await nameNewAnnotation();
await clickTermsOfService();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
export async function isElementPresent(selector: any, options: any) {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 2.
return Boolean(await page.$(selector, options));
}
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
-4
View File
@@ -1,11 +1,7 @@
// 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,12 +1,8 @@
// 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,16 +20,7 @@ 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({});
@@ -39,16 +30,7 @@ describe("cascade", () => {
], ],
[ [
"bar", "bar",
( (currentState, action, nextSharedState, prevSharedState) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
currentState: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
action: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
nextSharedState: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
prevSharedState: any
) => {
expect(currentState).toBeUndefined(); expect(currentState).toBeUndefined();
expect(action).toEqual(topLevelAction); expect(action).toEqual(topLevelAction);
expect(nextSharedState).toStrictEqual({ foo: 0 }); expect(nextSharedState).toStrictEqual({ foo: 0 });
@@ -501,7 +501,6 @@ 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",
@@ -514,7 +513,6 @@ 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",
@@ -1,12 +1,9 @@
import { Reducer } from "redux";
import undoable from "../../src/reducers/undoable"; import undoable from "../../src/reducers/undoable";
describe("create", () => { describe("create", () => {
test("no keys", () => { test("no keys", () => {
expect(() => expect(() => undoable(() => {})).toThrow();
undoable(() => {}, undefined as unknown as string[]) expect(() => undoable(() => {}, null)).toThrow();
).toThrow();
expect(() => undoable(() => {}, null as unknown as string[])).toThrow();
expect(() => undoable(() => {}, [])).toThrow(); expect(() => undoable(() => {}, [])).toThrow();
expect(() => undoable(() => {}, [], {})).toThrow(); expect(() => undoable(() => {}, [], {})).toThrow();
}); });
@@ -26,7 +23,7 @@ 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: Reducer = (state) => ({ a: state.a + 1, b: state.b + 1 }); const reducer = (state) => ({ 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" });
@@ -44,8 +41,8 @@ describe("undo", () => {
describe("redo", () => { describe("redo", () => {
const initialState = { a: 0, b: 1000 }; const initialState = { a: 0, b: 1000 };
const reducer: Reducer = (state) => ({ a: state.a + 1, b: state.b + 1 }); const reducer = (state) => ({ a: state.a + 1, b: state.b + 1 });
let UR: Reducer; let UR;
beforeEach(() => { beforeEach(() => {
UR = undoable(reducer, ["a"]); UR = undoable(reducer, ["a"]);
@@ -5,6 +5,5 @@ 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;
@@ -11,18 +11,14 @@ describe("rangeEncodeIndices", () => {
test("sorted flag", () => { test("sorted flag", () => {
expect(rangeEncodeIndices([1, 9, 432], 10, true)).toMatchObject([ expect(rangeEncodeIndices([1, 9, 432], 10, true)).toMatchObject([
1, 1, 9, 432,
9,
432,
]); ]);
expect(rangeEncodeIndices([1, 9, 432], 10, false)).toMatchObject([ expect(rangeEncodeIndices([1, 9, 432], 10, false)).toMatchObject([
1, 1, 9, 432,
9,
432,
]); ]);
expect( expect(rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true)).toMatchObject(
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true) [[0, 3], [9, 10], 432]
).toMatchObject([[0, 3], [9, 10], 432]); );
expect( expect(
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, false) rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, false)
).toMatchObject([[0, 3], [9, 10], 432]); ).toMatchObject([[0, 3], [9, 10], 432]);
@@ -10,18 +10,14 @@ import {
isubsetMask, isubsetMask,
} from "../../../src/annoMatrix"; } from "../../../src/annoMatrix";
import { Dataframe } from "../../../src/util/dataframe"; import { Dataframe } from "../../../src/util/dataframe";
import { Field } from "../../../src/common/types/schema";
enableFetchMocks(); enableFetchMocks();
describe("AnnoMatrix", () => { describe("AnnoMatrix", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let annoMatrix;
let annoMatrix: any;
beforeEach(async () => { beforeEach(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.resetMocks(); // reset all fetch mocking state
(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
@@ -35,67 +31,58 @@ describe("AnnoMatrix", () => {
expect(annoMatrix.nObs).toEqual(serverMocks.schema.schema.dataframe.nObs); expect(annoMatrix.nObs).toEqual(serverMocks.schema.schema.dataframe.nObs);
expect(annoMatrix.nVar).toEqual(serverMocks.schema.schema.dataframe.nVar); expect(annoMatrix.nVar).toEqual(serverMocks.schema.schema.dataframe.nVar);
expect(annoMatrix.isView).toBeFalsy(); expect(annoMatrix.isView).toBeFalsy();
expect(annoMatrix.viewOf).toBe(annoMatrix); expect(annoMatrix.viewOf).toBeUndefined();
expect(annoMatrix.rowIndex).toBeDefined(); expect(annoMatrix.rowIndex).toBeDefined();
}); });
test("simple single column fetch", async () => { test("simple single column fetch", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.annotationsObs(["name_0"]));
(fetch as any).once(serverMocks.annotationsObs(["name_0"]));
const df = await annoMatrix.fetch(Field.obs, "name_0"); const df = await annoMatrix.fetch("obs", "name_0");
expect(df).toBeInstanceOf(Dataframe); expect(df).toBeInstanceOf(Dataframe);
expect(df.colIndex.labels()).toEqual(["name_0"]); expect(df.colIndex.labels()).toEqual(["name_0"]);
expect(df.dims).toEqual([annoMatrix.nObs, 1]); expect(df.dims).toEqual([annoMatrix.nObs, 1]);
}); });
test("simple multi column fetch", async () => { test("simple multi column fetch", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch
(fetch as any)
.once(serverMocks.annotationsObs(["name_0"])) .once(serverMocks.annotationsObs(["name_0"]))
.once(serverMocks.annotationsObs(["n_genes"])); .once(serverMocks.annotationsObs(["n_genes"]));
await expect( await expect(
annoMatrix.fetch(Field.obs, ["name_0", "n_genes"]) annoMatrix.fetch("obs", ["name_0", "n_genes"])
).resolves.toBeInstanceOf(Dataframe); ).resolves.toBeInstanceOf(Dataframe);
}); });
describe("fetch from field", () => { describe("fetch from field", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. const getLastTwo = async (field) => {
const getLastTwo = async (field: any) => {
const columnNames = annoMatrix.getMatrixColumns(field).slice(-2); const columnNames = annoMatrix.getMatrixColumns(field).slice(-2);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockResponses(...columnNames.map(() => serverMocks.responder));
(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);
}; };
test(Field.obs, async () => getLastTwo(Field.obs)); test("obs", async () => getLastTwo("obs"));
test("var", async () => getLastTwo("var")); test("var", async () => getLastTwo("var"));
test("emb", async () => getLastTwo("emb")); test("emb", async () => getLastTwo("emb"));
}); });
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
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.annotationsObs(["n_genes"]));
(fetch as any).once(serverMocks.annotationsObs(["n_genes"])); await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf(
await expect( Dataframe
annoMatrix.fetch(Field.obs, "n_genes") );
).resolves.toBeInstanceOf(Dataframe);
// array of column names, expecting n_genes to be cached. // array of column names, expecting n_genes to be cached.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.annotationsObs(["percent_mito"]));
(fetch as any).once(serverMocks.annotationsObs(["percent_mito"]));
await expect( await expect(
annoMatrix.fetch(Field.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
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.responder);
(fetch as any).once(serverMocks.responder);
await expect( await expect(
annoMatrix.fetch("X", { annoMatrix.fetch("X", {
where: { where: {
@@ -108,8 +95,7 @@ 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;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch
(fetch as any)
.once( .once(
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]]) serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]])
) )
@@ -152,9 +138,9 @@ describe("AnnoMatrix", () => {
test("schema accessors", () => { test("schema accessors", () => {
expect(annoMatrix.getMatrixFields()).toEqual( expect(annoMatrix.getMatrixFields()).toEqual(
expect.arrayContaining(["X", Field.obs, "emb", "var"]) expect.arrayContaining(["X", "obs", "emb", "var"])
); );
expect(annoMatrix.getMatrixColumns(Field.obs)).toEqual( expect(annoMatrix.getMatrixColumns("obs")).toEqual(
expect.arrayContaining(["name_0", "n_genes", "louvain"]) expect.arrayContaining(["name_0", "n_genes", "louvain"])
); );
expect(annoMatrix.getColumnSchema("emb", "umap")).toEqual({ expect(annoMatrix.getColumnSchema("emb", "umap")).toEqual({
@@ -172,7 +158,7 @@ describe("AnnoMatrix", () => {
test the mask & label access to subset via isubset and isubsetMask test the mask & label access to subset via isubset and isubsetMask
*/ */
test("isubset", async () => { test("isubset", async () => {
const rowList = new Int32Array([0, 10]); const rowList = [0, 10];
const rowMask = new Uint8Array(annoMatrix.nObs); const rowMask = new Uint8Array(annoMatrix.nObs);
for (let i = 0; i < rowList.length; i += 1) { for (let i = 0; i < rowList.length; i += 1) {
rowMask[rowList[i]] = 1; rowMask[rowList[i]] = 1;
@@ -185,14 +171,11 @@ 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);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch
(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(Field.obs, "n_genes")) as Dataframe; const ng1 = await am1.fetch("obs", "n_genes");
const ng2 = (await am2.fetch(Field.obs, "n_genes")) as Dataframe; const ng2 = await am2.fetch("obs", "n_genes");
expect(ng1).toBeDefined();
expect(ng2).toBeDefined();
expect(ng1).toHaveLength(ng2.length); expect(ng1).toHaveLength(ng2.length);
expect(ng1.colIndex.labels()).toEqual(ng2.colIndex.labels()); expect(ng1.colIndex.labels()).toEqual(ng2.colIndex.labels());
expect(ng1.col("n_genes").asArray()).toEqual( expect(ng1.col("n_genes").asArray()).toEqual(
@@ -202,12 +185,10 @@ 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(Field.obs)).not.toContain("foo"); expect(base.getMatrixColumns("obs")).not.toContain("foo");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name")); await expect(base.fetch("obs", "foo")).rejects.toThrow(
await expect(base.fetch(Field.obs, "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
@@ -217,9 +198,9 @@ describe("AnnoMatrix", () => {
Float32Array, Float32Array,
0 0
); );
expect(base.getMatrixColumns(Field.obs)).not.toContain("foo"); expect(base.getMatrixColumns("obs")).not.toContain("foo");
expect(am1.getMatrixColumns(Field.obs)).toContain("foo"); expect(am1.getMatrixColumns("obs")).toContain("foo");
const foo: Dataframe = await am1.fetch(Field.obs, "foo"); const foo = await am1.fetch("obs", "foo");
expect(foo).toBeDefined(); expect(foo).toBeDefined();
expect(foo).toBeInstanceOf(Dataframe); expect(foo).toBeInstanceOf(Dataframe);
expect(foo).toHaveLength(am1.nObs); expect(foo).toHaveLength(am1.nObs);
@@ -229,11 +210,10 @@ describe("AnnoMatrix", () => {
/* drop */ /* drop */
const am2 = am1.dropObsColumn("foo"); const am2 = am1.dropObsColumn("foo");
expect(base.getMatrixColumns(Field.obs)).not.toContain("foo"); expect(base.getMatrixColumns("obs")).not.toContain("foo");
expect(am2.getMatrixColumns(Field.obs)).not.toContain("foo"); expect(am2.getMatrixColumns("obs")).not.toContain("foo");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name")); await expect(am2.fetch("obs", "foo")).rejects.toThrow(
await expect(am2.fetch(Field.obs, "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
} }
@@ -246,25 +226,23 @@ describe("AnnoMatrix", () => {
const am1 = clip(annoMatrix, 0.1, 0.9); const am1 = clip(annoMatrix, 0.1, 0.9);
await addDrop(am1); await addDrop(am1);
const am2 = isubset(am1, new Int32Array([0, 1, 2, 20, 30, 400])); const am2 = isubset(am1, [0, 1, 2, 20, 30, 400]);
await addDrop(am2); await addDrop(am2);
const am3 = isubset(annoMatrix, new Int32Array([10, 0, 7, 3])); const am3 = isubset(annoMatrix, [10, 0, 7, 3]);
await addDrop(am3); await addDrop(am3);
const am4 = clip(am3, 0, 1); const am4 = clip(am3, 0, 1);
await addDrop(am4); await addDrop(am4);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockResponse(serverMocks.responder);
(fetch as any).mockResponse(serverMocks.responder);
await am1.fetch(Field.obs, am1.getMatrixColumns(Field.obs)); await am1.fetch("obs", am1.getMatrixColumns("obs"));
await am2.fetch(Field.obs, am2.getMatrixColumns(Field.obs)); await am2.fetch("obs", am2.getMatrixColumns("obs"));
await am3.fetch(Field.obs, am3.getMatrixColumns(Field.obs)); await am3.fetch("obs", am3.getMatrixColumns("obs"));
await am4.fetch(Field.obs, am4.getMatrixColumns(Field.obs)); await am4.fetch("obs", am4.getMatrixColumns("obs"));
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.resetMocks();
(fetch as any).resetMocks();
await addDrop(am1); await addDrop(am1);
await addDrop(am2); await addDrop(am2);
@@ -274,7 +252,6 @@ 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(
@@ -288,7 +265,7 @@ describe("AnnoMatrix", () => {
"unassigned" "unassigned"
); );
const testVal = await am.fetch(Field.obs, "test"); const testVal = await am.fetch("obs", "test");
expect(testVal.col("test").asArray()).toEqual( expect(testVal.col("test").asArray()).toEqual(
new Array(am.nObs).fill("unassigned") new Array(am.nObs).fill("unassigned")
); );
@@ -296,7 +273,7 @@ describe("AnnoMatrix", () => {
/* set values in column */ /* set values in column */
const whichRows = [1, 2, 10]; const whichRows = [1, 2, 10];
const am1 = await am.setObsColumnValues("test", whichRows, "yo"); const am1 = await am.setObsColumnValues("test", whichRows, "yo");
const testVal1 = await am1.fetch(Field.obs, "test"); const testVal1 = await am1.fetch("obs", "test");
const expt = new Array(am1.nObs).fill("unassigned"); const expt = new Array(am1.nObs).fill("unassigned");
for (let i = 0; i < whichRows.length; i += 1) { for (let i = 0; i < whichRows.length; i += 1) {
const offset = am1.rowIndex.getOffset(whichRows[i]); const offset = am1.rowIndex.getOffset(whichRows[i]);
@@ -304,16 +281,15 @@ describe("AnnoMatrix", () => {
} }
expect(testVal1).not.toBe(testVal); expect(testVal1).not.toBe(testVal);
expect(testVal1.col("test").asArray()).toEqual(expt); expect(testVal1.col("test").asArray()).toEqual(expt);
expect(am1.getColumnSchema(Field.obs, "test").type).toBe("categorical"); expect(am1.getColumnSchema("obs", "test").type).toBe("categorical");
expect(am1.getColumnSchema(Field.obs, "test").categories).toEqual( expect(am1.getColumnSchema("obs", "test").categories).toEqual(
expect.arrayContaining(["unassigned", "red", "green", "yo"]) expect.arrayContaining(["unassigned", "red", "green", "yo"])
); );
/* drop column */ /* drop column */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name"));
am = am1.dropObsColumn("test"); am = am1.dropObsColumn("test");
await expect(am.fetch(Field.obs, "test")).rejects.toThrow( await expect(am.fetch("obs", "test")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
} }
@@ -326,18 +302,17 @@ describe("AnnoMatrix", () => {
const am1 = clip(annoMatrix, 0.1, 0.9); const am1 = clip(annoMatrix, 0.1, 0.9);
await addSetDrop(am1); await addSetDrop(am1);
const am2 = isubset(am1, new Int32Array([0, 1, 2, 10, 20, 30, 400])); const am2 = isubset(am1, [0, 1, 2, 10, 20, 30, 400]);
await addSetDrop(am2); await addSetDrop(am2);
const am3 = isubset(annoMatrix, new Int32Array([10, 1, 0, 30, 2])); const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]);
await addSetDrop(am3); await addSetDrop(am3);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockResponse(serverMocks.responder);
(fetch as any).mockResponse(serverMocks.responder);
await am1.fetch(Field.obs, am1.getMatrixColumns(Field.obs)); await am1.fetch("obs", am1.getMatrixColumns("obs"));
await am2.fetch(Field.obs, am2.getMatrixColumns(Field.obs)); await am2.fetch("obs", am2.getMatrixColumns("obs"));
await am3.fetch(Field.obs, am3.getMatrixColumns(Field.obs)); await am3.fetch("obs", am3.getMatrixColumns("obs"));
await addSetDrop(am1); await addSetDrop(am1);
await addSetDrop(am2); await addSetDrop(am2);
@@ -12,25 +12,19 @@ import {
AnnoMatrixObsCrossfilter, AnnoMatrixObsCrossfilter,
isubsetMask, isubsetMask,
} from "../../../src/annoMatrix"; } from "../../../src/annoMatrix";
import { Dataframe } from "../../../src/util/dataframe";
import { rangeFill } from "../../../src/util/range"; import { rangeFill } from "../../../src/util/range";
import { Field, Schema } from "../../../src/common/types/schema";
enableFetchMocks(); enableFetchMocks();
describe("AnnoMatrixCrossfilter", () => { describe("AnnoMatrixCrossfilter", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let annoMatrix;
let annoMatrix: any; let crossfilter;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let crossfilter: any;
beforeEach(async () => { beforeEach(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.resetMocks(); // reset all fetch mocking state
(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 as Schema serverMocks.schema.schema
); );
crossfilter = new AnnoMatrixObsCrossfilter(annoMatrix); crossfilter = new AnnoMatrixObsCrossfilter(annoMatrix);
}); });
@@ -73,11 +67,8 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.obsCrossfilter.hasDimension("obs/louvain") crossfilter.obsCrossfilter.hasDimension("obs/louvain")
).toBeFalsy(); ).toBeFalsy();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
(fetch as any).once( let newCrossfilter = await crossfilter.select("obs", "louvain", {
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
let newCrossfilter = await crossfilter.select(Field.obs, "louvain", {
mode: "none", mode: "none",
}); });
@@ -85,10 +76,9 @@ describe("AnnoMatrixCrossfilter", () => {
expect( expect(
newCrossfilter.obsCrossfilter.hasDimension("obs/louvain") newCrossfilter.obsCrossfilter.hasDimension("obs/louvain")
).toBeTruthy(); ).toBeTruthy();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. expect(fetch.mock.calls).toHaveLength(1);
expect((fetch as any).mock.calls).toHaveLength(1);
newCrossfilter = await crossfilter.select(Field.obs, "louvain", { newCrossfilter = await crossfilter.select("obs", "louvain", {
mode: "all", mode: "all",
}); });
expect(newCrossfilter.countSelected()).toEqual(annoMatrix.nObs); expect(newCrossfilter.countSelected()).toEqual(annoMatrix.nObs);
@@ -97,11 +87,8 @@ describe("AnnoMatrixCrossfilter", () => {
test("simple column select", async () => { test("simple column select", async () => {
let xfltr; let xfltr;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
(fetch as any).once( xfltr = await crossfilter.select("obs", "louvain", {
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
xfltr = await crossfilter.select(Field.obs, "louvain", {
mode: "exact", mode: "exact",
values: ["NK cells", "B cells"], values: ["NK cells", "B cells"],
}); });
@@ -118,7 +105,6 @@ 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;
}, []) }, [])
@@ -134,20 +120,17 @@ describe("AnnoMatrixCrossfilter", () => {
) )
); );
const df: Dataframe = await annoMatrix.fetch(Field.obs, "louvain"); const df = await annoMatrix.fetch("obs", "louvain");
const values = df.col("louvain").asArray(); const values = df.col("louvain").asArray();
const selected = xfltr.allSelectedMask(); const selected = xfltr.allSelectedMask();
values.every( values.every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
(val: any, idx: any) =>
!["NK cells", "B cells"].includes(val) !== !selected[idx]
); );
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(
(fetch as any).once(
serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)]) serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)])
); );
xfltr = await xfltr.select(Field.obs, "n_genes", { xfltr = await xfltr.select("obs", "n_genes", {
mode: "range", mode: "range",
lo: 0, lo: 0,
hi: 500, hi: 500,
@@ -163,7 +146,6 @@ 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;
}, []) }, [])
@@ -178,8 +160,7 @@ 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;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(
(fetch as any).once(
serverMocks.dataframeResponse( serverMocks.dataframeResponse(
["TEST"], ["TEST"],
[rangeFill(new Float32Array(nObs), 0, 0.1)] [rangeFill(new Float32Array(nObs), 0, 0.1)]
@@ -215,19 +196,14 @@ describe("AnnoMatrixCrossfilter", () => {
}); });
const values = df.icol(0).asArray(); const values = df.icol(0).asArray();
const selected = xfltr.allSelectedMask(); const selected = xfltr.allSelectedMask();
values.every( values.every((val, idx) => !(val >= 0 && val <= 50) !== !selected[idx]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. expect(selected.reduce((acc, val) => (val ? acc + 1 : acc), 0)).toEqual(
(val: any, idx: any) => !(val >= 0 && val <= 50) !== !selected[idx] xfltr.countSelected()
); );
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 () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(
(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])]
@@ -246,7 +222,6 @@ 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);
@@ -255,11 +230,8 @@ describe("AnnoMatrixCrossfilter", () => {
let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset); let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset);
expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs); expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
(fetch as any).once( xfltr = await xfltr.select("obs", "louvain", {
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
xfltr = await xfltr.select(Field.obs, "louvain", {
mode: "exact", mode: "exact",
values: ["NK cells", "B cells"], values: ["NK cells", "B cells"],
}); });
@@ -267,17 +239,11 @@ describe("AnnoMatrixCrossfilter", () => {
expect(xfltr).toBeDefined(); expect(xfltr).toBeDefined();
expect(xfltr.countSelected()).toEqual(240); expect(xfltr.countSelected()).toEqual(240);
const df: Dataframe = (await annoMatrixSubset.fetch( const df = await annoMatrixSubset.fetch("obs", "louvain");
Field.obs,
"louvain"
)) as Dataframe;
expect(df).toBeDefined();
const values = df.col("louvain").asArray(); const values = df.col("louvain").asArray();
const selected = xfltr.allSelectedMask(); const selected = xfltr.allSelectedMask();
values.every( values.every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
(val: any, idx: any) =>
!["NK cells", "B cells"].includes(val) !== !selected[idx]
); );
}); });
@@ -290,9 +256,8 @@ describe("AnnoMatrixCrossfilter", () => {
"unable to obsSelect upon the var dimension" "unable to obsSelect upon the var dimension"
); );
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name")); await expect(crossfilter.select("obs", "foo")).rejects.toThrow(
await expect(crossfilter.select(Field.obs, "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
}); });
@@ -302,32 +267,27 @@ describe("AnnoMatrixCrossfilter", () => {
/* /*
test the matrix mutators via crossfilter proxy test the matrix mutators via crossfilter proxy
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function helperAddTestCol(cf, colName, colSchema = null) {
async function helperAddTestCol(cf: any, colName: any, colSchema = null) {
expect( expect(
cf.annoMatrix.getMatrixColumns(Field.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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (v) => v.name === colName
(v: any) => v.name === colName
) )
).toHaveLength(1); ).toHaveLength(1);
const df = await xfltr.annoMatrix.fetch(Field.obs, colName); const df = await xfltr.annoMatrix.fetch("obs", colName);
expect(df.hasCol(colName)).toBeTruthy(); expect(df.hasCol(colName)).toBeTruthy();
return xfltr; return xfltr;
} }
@@ -335,7 +295,7 @@ describe("AnnoMatrixCrossfilter", () => {
test("addObsColumn", async () => { test("addObsColumn", async () => {
expect(crossfilter.countSelected()).toBe(annoMatrix.nObs); expect(crossfilter.countSelected()).toBe(annoMatrix.nObs);
expect( expect(
crossfilter.annoMatrix.getMatrixColumns(Field.obs).includes("foo") crossfilter.annoMatrix.getMatrixColumns("obs").includes("foo")
).toBeFalsy(); ).toBeFalsy();
const xfltr = crossfilter.addObsColumn( const xfltr = crossfilter.addObsColumn(
{ name: "foo", type: "categorical", categories: ["A"] }, { name: "foo", type: "categorical", categories: ["A"] },
@@ -346,7 +306,7 @@ describe("AnnoMatrixCrossfilter", () => {
// check schema updates correctly. // check schema updates correctly.
expect(xfltr.countSelected()).toBe(annoMatrix.nObs); expect(xfltr.countSelected()).toBe(annoMatrix.nObs);
expect( expect(
xfltr.annoMatrix.getMatrixColumns(Field.obs).includes("foo") xfltr.annoMatrix.getMatrixColumns("obs").includes("foo")
).toBeTruthy(); ).toBeTruthy();
expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toMatchObject({ expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toMatchObject({
name: "foo", name: "foo",
@@ -354,18 +314,17 @@ describe("AnnoMatrixCrossfilter", () => {
}); });
expect( expect(
xfltr.annoMatrix.schema.annotations.obs.columns.filter( xfltr.annoMatrix.schema.annotations.obs.columns.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (v) => v.name === "foo"
(v: any) => v.name === "foo"
) )
).toHaveLength(1); ).toHaveLength(1);
// check data update. // check data update.
const df: Dataframe = await xfltr.annoMatrix.fetch(Field.obs, "foo"); const df = await xfltr.annoMatrix.fetch("obs", "foo");
expect( expect(
df df
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.every((v: any) => v === "A") .every((v) => v === "A")
).toBeTruthy(); ).toBeTruthy();
// check that we catch dups // check that we catch dups
@@ -402,30 +361,27 @@ 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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (v) => v.name === "foo"
(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();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name")); await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
await expect(xfltr.annoMatrix.fetch(Field.obs, "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
// now same, but ensure we have built an index before doing the drop // now same, but ensure we have built an index before doing the drop
xfltr = await helperAddTestCol(crossfilter, "bar"); xfltr = await helperAddTestCol(crossfilter, "bar");
xfltr = await xfltr.select(Field.obs, "bar", { xfltr = await xfltr.select("obs", "bar", {
mode: "exact", mode: "exact",
values: "whatever", values: "whatever",
}); });
xfltr = xfltr.dropObsColumn("bar"); xfltr = xfltr.dropObsColumn("bar");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name")); await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
await expect( "unknown column name"
xfltr.select(Field.obs, "bar", { mode: "all" }) );
).rejects.toThrow("unknown column name");
}); });
test("renameObsColumn", async () => { test("renameObsColumn", async () => {
@@ -442,43 +398,38 @@ describe("AnnoMatrixCrossfilter", () => {
// add a column, then rename it. // add a column, then rename it.
xfltr = await helperAddTestCol(crossfilter, "foo"); xfltr = await helperAddTestCol(crossfilter, "foo");
xfltr = xfltr.renameObsColumn("foo", "bar"); xfltr = xfltr.renameObsColumn("foo", "bar");
expect( expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toBeUndefined();
xfltr.annoMatrix.getColumnSchema(Field.obs, "foo") expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({
).toBeUndefined();
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "bar")).toMatchObject({
name: "bar", name: "bar",
type: "categorical", type: "categorical",
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name")); await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
await expect(xfltr.annoMatrix.fetch(Field.obs, "foo")).rejects.toThrow(
"unknown column name" "unknown column name"
); );
const df = await xfltr.annoMatrix.fetch(Field.obs, "bar"); const df = await xfltr.annoMatrix.fetch("obs", "bar");
expect(df.hasCol("bar")).toBeTruthy(); expect(df.hasCol("bar")).toBeTruthy();
// now same, but ensure we have built an index before doing the rename // now same, but ensure we have built an index before doing the rename
xfltr = await helperAddTestCol(crossfilter, "bar"); xfltr = await helperAddTestCol(crossfilter, "bar");
xfltr = await xfltr.select(Field.obs, "bar", { xfltr = await xfltr.select("obs", "bar", {
mode: "exact", mode: "exact",
values: "whatever", values: "whatever",
}); });
xfltr = xfltr.renameObsColumn("bar", "xyz"); xfltr = xfltr.renameObsColumn("bar", "xyz");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.mockRejectOnce(new Error("unknown column name"));
(fetch as any).mockRejectOnce(new Error("unknown column name")); await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
"unknown column name"
);
await expect( await expect(
xfltr.select(Field.obs, "bar", { mode: "all" }) xfltr.select("obs", "xyz", { mode: "none" })
).rejects.toThrow("unknown column name");
await expect(
xfltr.select(Field.obs, "xyz", { mode: "none" })
).resolves.toBeInstanceOf(AnnoMatrixObsCrossfilter); ).resolves.toBeInstanceOf(AnnoMatrixObsCrossfilter);
}); });
test("addObsAnnoCategory", async () => { test("addObsAnnoCategory", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let xfltr;
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(
@@ -489,14 +440,13 @@ 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",
categories: ["unassigned"], categories: ["unassigned"],
}); });
xfltr = xfltr.addObsAnnoCategory("foo", "a-new-label"); xfltr = xfltr.addObsAnnoCategory("foo", "a-new-label");
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject({ expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo", name: "foo",
type: "categorical", type: "categorical",
categories: expect.arrayContaining(["a-new-label", "unassigned"]), categories: expect.arrayContaining(["a-new-label", "unassigned"]),
@@ -508,18 +458,17 @@ 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",
categories: ["unassigned"], categories: ["unassigned"],
}); });
xfltr = await xfltr.select(Field.obs, "bar", { xfltr = await xfltr.select("obs", "bar", {
mode: "exact", mode: "exact",
values: "something", values: "something",
}); });
xfltr = xfltr.addObsAnnoCategory("bar", "a-new-label"); xfltr = xfltr.addObsAnnoCategory("bar", "a-new-label");
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "bar")).toMatchObject({ expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({
name: "bar", name: "bar",
type: "categorical", type: "categorical",
categories: expect.arrayContaining(["a-new-label", "unassigned"]), categories: expect.arrayContaining(["a-new-label", "unassigned"]),
@@ -537,20 +486,19 @@ 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",
categories: ["unassigned", "red", "green", "blue"], categories: ["unassigned", "red", "green", "blue"],
}); });
xfltr = await xfltr.select(Field.obs, "foo", { mode: "all" }); xfltr = await xfltr.select("obs", "foo", { mode: "all" });
expect( expect(
(await xfltr.annoMatrix.fetch(Field.obs, "foo")) (await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.every((v: any) => v === "unassigned") .every((v) => v === "unassigned")
).toBeTruthy(); ).toBeTruthy();
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject({ expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo", name: "foo",
type: "categorical", type: "categorical",
categories: expect.arrayContaining([ categories: expect.arrayContaining([
@@ -564,23 +512,21 @@ describe("AnnoMatrixCrossfilter", () => {
// remove an unused category // remove an unused category
const xfltr1 = await xfltr.removeObsAnnoCategory("foo", "red", "mumble"); const xfltr1 = await xfltr.removeObsAnnoCategory("foo", "red", "mumble");
expect( expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.every((v: any) => v === "unassigned") .every((v) => v === "unassigned")
).toBeTruthy(); ).toBeTruthy();
expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
{ name: "foo",
name: "foo", type: "categorical",
type: "categorical", categories: expect.arrayContaining([
categories: expect.arrayContaining([ "unassigned",
"unassigned", "green",
"green", "blue",
"blue", "mumble",
"mumble", ]),
]), });
}
);
// remove a used category // remove a used category
const xfltr2 = await xfltr.removeObsAnnoCategory( const xfltr2 = await xfltr.removeObsAnnoCategory(
@@ -589,18 +535,16 @@ describe("AnnoMatrixCrossfilter", () => {
"red" "red"
); );
expect( expect(
(await xfltr2.annoMatrix.fetch(Field.obs, "foo")) (await xfltr2.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.every((v: any) => v === "red") .every((v) => v === "red")
).toBeTruthy(); ).toBeTruthy();
expect(xfltr2.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
{ name: "foo",
name: "foo", type: "categorical",
type: "categorical", categories: expect.arrayContaining(["green", "blue", "red"]),
categories: expect.arrayContaining(["green", "blue", "red"]), });
}
);
}); });
test("setObsColumnValues", async () => { test("setObsColumnValues", async () => {
@@ -612,13 +556,12 @@ 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",
categories: ["unassigned", "red", "green", "blue"], categories: ["unassigned", "red", "green", "blue"],
}); });
xfltr = await xfltr.select(Field.obs, "foo", { mode: "all" }); xfltr = await xfltr.select("obs", "foo", { mode: "all" });
// catch unknown row label // catch unknown row label
await expect(() => await expect(() =>
@@ -627,43 +570,40 @@ describe("AnnoMatrixCrossfilter", () => {
// set a few rows // set a few rows
expect( expect(
(await xfltr.annoMatrix.fetch(Field.obs, "foo")) (await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.every((v: any) => v === "unassigned") .every((v) => v === "unassigned")
).toBeTruthy(); ).toBeTruthy();
const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple"); const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
expect( expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() .asArray()
.every( .every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (v, i) =>
(v: any, i: any) =>
v === "unassigned" || (v === "purple" && (i === 0 || i === 10)) v === "unassigned" || (v === "purple" && (i === 0 || i === 10))
) )
).toBeTruthy(); ).toBeTruthy();
expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
{ name: "foo",
name: "foo", type: "categorical",
type: "categorical", categories: expect.arrayContaining([
categories: expect.arrayContaining([ "unassigned",
"unassigned", "red",
"red", "green",
"green", "blue",
"blue", "purple",
"purple", ]),
]), });
}
);
expect(xfltr1.countSelected()).toEqual(xfltr1.annoMatrix.nObs); expect(xfltr1.countSelected()).toEqual(xfltr1.annoMatrix.nObs);
const xfltr2 = await xfltr1.select(Field.obs, "foo", { const xfltr2 = await xfltr1.select("obs", "foo", {
mode: "exact", mode: "exact",
values: ["purple"], values: ["purple"],
}); });
expect(xfltr2.countSelected()).toEqual(2); expect(xfltr2.countSelected()).toEqual(2);
expect(xfltr2.allSelectedLabels()).toEqual(Array.from([0, 10])); expect(xfltr2.allSelectedLabels()).toEqual(Int32Array.from([0, 10]));
}); });
test("resetObsColumnValues", async () => { test("resetObsColumnValues", async () => {
@@ -675,13 +615,12 @@ 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",
categories: ["unassigned", "red", "green", "blue"], categories: ["unassigned", "red", "green", "blue"],
}); });
xfltr = await xfltr.select(Field.obs, "foo", { xfltr = await xfltr.select("obs", "foo", {
mode: "exact", mode: "exact",
values: "red", values: "red",
}); });
@@ -692,60 +631,54 @@ describe("AnnoMatrixCrossfilter", () => {
).rejects.toThrow("unknown category"); ).rejects.toThrow("unknown category");
let xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple"); let xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
xfltr1 = await xfltr1.select(Field.obs, "foo", { xfltr1 = await xfltr1.select("obs", "foo", {
mode: "exact", mode: "exact",
values: "purple", values: "purple",
}); });
expect( expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.filter((v: any) => v === "purple") .filter((v) => 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(Field.obs, "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.filter((v: any) => v === "magenta") .filter((v) => v === "magenta")
).toHaveLength(2); ).toHaveLength(2);
expect( expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo")) (await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo") .col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .asArray()
.filter((v: any) => v === "purple") .filter((v) => v === "purple")
).toHaveLength(0); ).toHaveLength(0);
expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
{ name: "foo",
name: "foo", type: "categorical",
type: "categorical", categories: expect.arrayContaining([
categories: expect.arrayContaining([ "unassigned",
"unassigned", "red",
"red", "green",
"green", "blue",
"blue", "purple",
"purple", "magenta",
"magenta", ]),
]), });
}
);
}); });
}); });
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
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
(fetch as any).once( const xfltr = await crossfilter.select("obs", "louvain", {
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
const xfltr = await crossfilter.select(Field.obs, "louvain", {
mode: "exact", mode: "exact",
values: "B cells", values: "B cells",
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. expect(fetch.mock.calls).toHaveLength(1);
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(
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,6 @@
export const baseDataURL = "https://a.fake.url/api/v0.2"; export const baseDataURL = "https://a.fake.url/api/v0.2";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. window.CELLXGENE = {
(window as any).CELLXGENE = {
API: { API: {
prefix: baseDataURL, prefix: baseDataURL,
version: "v0.2/", version: "v0.2/",
@@ -0,0 +1,211 @@
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])
);
}
@@ -1,249 +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]) ?? []
),
};
// 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("unknown 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));
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,
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])
);
}
@@ -1,6 +1,4 @@
import { RawSchema } from "../../../../src/common/types/schema"; export const schema = {
export const schema: { schema: RawSchema } = {
schema: { schema: {
annotations: { annotations: {
obs: { obs: {
File diff suppressed because it is too large Load Diff
@@ -4,34 +4,13 @@ import {
_whereCacheCreate, _whereCacheCreate,
_whereCacheMerge, _whereCacheMerge,
} from "../../../src/annoMatrix/whereCache"; } from "../../../src/annoMatrix/whereCache";
import { Field, Schema } from "../../../src/common/types/schema";
import { Query } from "../../../src/annoMatrix/query";
const schema = {} as Schema; const schema = {};
describe("whereCache", () => { describe("whereCache", () => {
test("whereCacheGet - where query, missing cache values", () => { test("whereCacheGet - where query, missing cache values", () => {
expect( expect(
_whereCacheGet({}, schema, Field.X, { _whereCacheGet({}, schema, "X", {
where: {
field: Field.var,
column: "foo",
value: "bar",
},
})
).toEqual([undefined]);
expect(
_whereCacheGet({}, schema, Field.X, {
summarize: {
method: "mean",
field: Field.var,
column: "foo",
values: ["bar"],
},
})
).toEqual([undefined]);
expect(
_whereCacheGet({ where: { X: {} } }, schema, Field.X, {
where: { where: {
field: "var", field: "var",
column: "foo", column: "foo",
@@ -40,7 +19,25 @@ describe("whereCache", () => {
}) })
).toEqual([undefined]); ).toEqual([undefined]);
expect( expect(
_whereCacheGet({ where: { X: { var: new Map() } } }, schema, Field.X, { _whereCacheGet({}, schema, "X", {
summarize: {
field: "var",
column: "foo",
values: ["bar"],
},
})
).toEqual([undefined]);
expect(
_whereCacheGet({ where: { X: {} } }, schema, "X", {
where: {
field: "var",
column: "foo",
value: "bar",
},
})
).toEqual([undefined]);
expect(
_whereCacheGet({ where: { X: { var: new Map() } } }, schema, "X", {
where: { where: {
field: "var", field: "var",
column: "foo", column: "foo",
@@ -52,7 +49,7 @@ describe("whereCache", () => {
_whereCacheGet( _whereCacheGet(
{ where: { X: { var: new Map([["foo", new Map()]]) } } }, { where: { X: { var: new Map([["foo", new Map()]]) } } },
schema, schema,
Field.X, "X",
{ {
where: { where: {
field: "var", field: "var",
@@ -66,7 +63,7 @@ describe("whereCache", () => {
test("whereCacheGet - summarize query, missing cache values", () => { test("whereCacheGet - summarize query, missing cache values", () => {
expect( expect(
_whereCacheGet({}, schema, Field.X, { _whereCacheGet({}, schema, "X", {
summarize: { summarize: {
method: "mean", method: "mean",
field: "var", field: "var",
@@ -79,7 +76,7 @@ describe("whereCache", () => {
_whereCacheGet( _whereCacheGet(
{ summarize: { X: { mean: { var: new Map() } } } }, { summarize: { X: { mean: { var: new Map() } } } },
schema, schema,
Field.X, "X",
{ {
summarize: { summarize: {
method: "mean", method: "mean",
@@ -125,7 +122,7 @@ describe("whereCache", () => {
}; };
expect( expect(
_whereCacheGet(whereCache, schema, Field.X, { _whereCacheGet(whereCache, schema, "X", {
where: { where: {
field: "var", field: "var",
column: "foo", column: "foo",
@@ -134,7 +131,7 @@ describe("whereCache", () => {
}) })
).toEqual([0]); ).toEqual([0]);
expect( expect(
_whereCacheGet(whereCache, schema, Field.X, { _whereCacheGet(whereCache, schema, "X", {
summarize: { summarize: {
method: "mean", method: "mean",
field: "var", field: "var",
@@ -144,7 +141,7 @@ describe("whereCache", () => {
}) })
).toEqual([0]); ).toEqual([0]);
expect( expect(
_whereCacheGet(whereCache, schema, Field.X, { _whereCacheGet(whereCache, schema, "X", {
where: { where: {
field: "var", field: "var",
column: "foo", column: "foo",
@@ -153,7 +150,7 @@ describe("whereCache", () => {
}) })
).toEqual([1, 2]); ).toEqual([1, 2]);
expect( expect(
_whereCacheGet(whereCache, schema, Field.X, { _whereCacheGet(whereCache, schema, "X", {
summarize: { summarize: {
method: "mean", method: "mean",
field: "var", field: "var",
@@ -162,12 +159,9 @@ describe("whereCache", () => {
}, },
}) })
).toEqual([1, 2]); ).toEqual([1, 2]);
expect(_whereCacheGet(whereCache, schema, "Y", {})).toEqual([undefined]);
expect( expect(
// Force invalid field value Y _whereCacheGet(whereCache, schema, "X", {
_whereCacheGet(whereCache, schema, "Y" as Field, {} as Query)
).toEqual([undefined]);
expect(
_whereCacheGet(whereCache, schema, Field.X, {
where: { where: {
field: "whoknows", field: "whoknows",
column: "whatever", column: "whatever",
@@ -176,7 +170,7 @@ describe("whereCache", () => {
}) })
).toEqual([undefined]); ).toEqual([undefined]);
expect( expect(
_whereCacheGet(whereCache, schema, Field.X, { _whereCacheGet(whereCache, schema, "X", {
where: { where: {
field: "var", field: "var",
column: "whatever", column: "whatever",
@@ -185,7 +179,7 @@ describe("whereCache", () => {
}) })
).toEqual([undefined]); ).toEqual([undefined]);
expect( expect(
_whereCacheGet(whereCache, schema, Field.X, { _whereCacheGet(whereCache, schema, "X", {
where: { where: {
field: "var", field: "var",
column: "foo", column: "foo",
@@ -204,7 +198,7 @@ describe("whereCache", () => {
}, },
}; };
const wc = _whereCacheCreate( const wc = _whereCacheCreate(
Field.obs, "field",
{ {
where: { where: {
field: "queryField", field: "queryField",
@@ -218,27 +212,18 @@ describe("whereCache", () => {
expect(wc).toEqual( expect(wc).toEqual(
expect.objectContaining({ expect.objectContaining({
where: { where: {
[Field.obs]: { field: {
queryField: expect.any(Map), queryField: expect.any(Map),
}, },
}, },
}) })
); );
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. expect(wc.where.field.queryField.has("queryColumn")).toEqual(true);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. expect(wc.where.field.queryField.get("queryColumn")).toBeInstanceOf(Map);
expect((wc.where as any)[Field.obs].queryField.has("queryColumn")).toEqual(true);
expect( expect(
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. wc.where.field.queryField.get("queryColumn").has("queryValue")
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(wc.where as any)[Field.obs].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.obs].queryField.get("queryColumn").has("queryValue")
).toEqual(true); ).toEqual(true);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
expect(_whereCacheGet(wc!, schema, Field.obs, query)).toEqual([0, 1, 2]);
}); });
test("whereCacheCreate, summarize query", () => { test("whereCacheCreate, summarize query", () => {
@@ -250,14 +235,12 @@ describe("whereCache", () => {
values: ["queryValue"], values: ["queryValue"],
}, },
}; };
const wc = _whereCacheCreate(Field.obs, query, [0, 1, 2]); const wc = _whereCacheCreate("field", query, [0, 1, 2]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
expect(_whereCacheGet(wc!, schema, Field.obs, query)).toEqual([0, 1, 2]);
}); });
test("whereCacheCreate, unknown query type", () => { test("whereCacheCreate, unknown query type", () => {
// @ts-expect-error --- force invalid query {foobar: true} expect(_whereCacheCreate("field", { foobar: true }, [1])).toEqual({});
expect(_whereCacheCreate(Field.obs, { foobar: true }, [1])).toEqual({});
}); });
test("whereCacheMerge, where queries", () => { test("whereCacheMerge, where queries", () => {
@@ -265,20 +248,19 @@ describe("whereCache", () => {
// remember, will mutate dst // remember, will mutate dst
const src = _whereCacheCreate( const src = _whereCacheCreate(
Field.obs, "field",
{ where: { field: "queryField", column: "queryColumn", value: "foo" } }, { where: { field: "queryField", column: "queryColumn", value: "foo" } },
["foo"] ["foo"]
); );
const dst1 = _whereCacheCreate( const dst1 = _whereCacheCreate(
Field.obs, "field",
{ where: { field: "queryField", column: "queryColumn", value: "bar" } }, { where: { field: "queryField", column: "queryColumn", value: "bar" } },
["dst1"] ["dst1"]
); );
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert dst1 and src to be non-null wc = _whereCacheMerge(dst1, src);
wc = _whereCacheMerge(dst1!, src!);
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
where: { where: {
field: "queryField", field: "queryField",
column: "queryColumn", column: "queryColumn",
@@ -287,7 +269,7 @@ describe("whereCache", () => {
}) })
).toEqual(["foo"]); ).toEqual(["foo"]);
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
where: { where: {
field: "queryField", field: "queryField",
column: "queryColumn", column: "queryColumn",
@@ -297,14 +279,13 @@ describe("whereCache", () => {
).toEqual(["dst1"]); ).toEqual(["dst1"]);
const dst2 = _whereCacheCreate( const dst2 = _whereCacheCreate(
Field.obs, "field",
{ where: { field: "queryField", column: "queryColumn", value: "bar" } }, { where: { field: "queryField", column: "queryColumn", value: "bar" } },
["dst2"] ["dst2"]
); );
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert dst2m, dst1 and src to be non-null wc = _whereCacheMerge(dst2, dst1, src);
wc = _whereCacheMerge(dst2!, dst1!, src!);
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
where: { where: {
field: "queryField", field: "queryField",
column: "queryColumn", column: "queryColumn",
@@ -313,7 +294,7 @@ describe("whereCache", () => {
}) })
).toEqual(["foo"]); ).toEqual(["foo"]);
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
where: { where: {
field: "queryField", field: "queryField",
column: "queryColumn", column: "queryColumn",
@@ -322,20 +303,17 @@ describe("whereCache", () => {
}) })
).toEqual(["dst1"]); ).toEqual(["dst1"]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert src to be non-null wc = _whereCacheMerge({}, src);
wc = _whereCacheMerge({}, src!);
expect(wc).toEqual(src); expect(wc).toEqual(src);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert src to be non-null wc = _whereCacheMerge({ where: { field: { queryField: new Map() } } }, src);
wc = _whereCacheMerge({ where: { obs: { queryField: new Map() } } }, src!);
expect(wc).toEqual(src); expect(wc).toEqual(src);
}); });
test("whereCacheMerge, mixed queries", () => { test("whereCacheMerge, mixed queries", () => {
const wc = _whereCacheMerge( const wc = _whereCacheMerge(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null
_whereCacheCreate( _whereCacheCreate(
Field.obs, "field",
{ {
where: { where: {
field: "queryField", field: "queryField",
@@ -344,10 +322,9 @@ describe("whereCache", () => {
}, },
}, },
["a"] ["a"]
)!, ),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null
_whereCacheCreate( _whereCacheCreate(
Field.obs, "field",
{ {
summarize: { summarize: {
method: "mean", method: "mean",
@@ -357,11 +334,11 @@ describe("whereCache", () => {
}, },
}, },
["b"] ["b"]
)! )
); );
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
where: { where: {
field: "queryField", field: "queryField",
column: "queryColumn", column: "queryColumn",
@@ -371,7 +348,7 @@ describe("whereCache", () => {
).toEqual(["a"]); ).toEqual(["a"]);
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
summarize: { summarize: {
method: "mean", method: "mean",
field: "queryField", field: "queryField",
@@ -382,7 +359,7 @@ describe("whereCache", () => {
).toEqual(["b"]); ).toEqual(["b"]);
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
where: { where: {
field: "queryField", field: "queryField",
column: "queryColumn", column: "queryColumn",
@@ -392,7 +369,7 @@ describe("whereCache", () => {
).toEqual([undefined]); ).toEqual([undefined]);
expect( expect(
_whereCacheGet(wc, schema, Field.obs, { _whereCacheGet(wc, schema, "field", {
summarize: { summarize: {
method: "no-such-method", method: "no-such-method",
field: "queryField", field: "queryField",
@@ -1,19 +1,16 @@
import cloneDeep from "lodash.clonedeep"; import cloneDeep from "lodash.clonedeep";
import { NumberArray } from "../../src/common/types/arraytypes";
import calcCentroid from "../../src/util/centroid"; import calcCentroid from "../../src/util/centroid";
import quantile from "../../src/util/quantile"; 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 { normalizeWritableCategoricalSchema } from "../../src/annoMatrix/normalize"; import { normalizeWritableCategoricalSchema } from "../../src/annoMatrix/normalize";
import { Dataframe } from "../../src/util/dataframe";
describe("centroid", () => { describe("centroid", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let schema;
let schema: any; let obsAnnotations;
let obsAnnotations: Dataframe; let obsLayout;
let obsLayout: Dataframe;
beforeAll(() => { beforeAll(() => {
schema = indexEntireSchema(cloneDeep(REST.schema.schema)); schema = indexEntireSchema(cloneDeep(REST.schema.schema));
@@ -43,12 +40,11 @@ describe("centroid", () => {
// This expected result assumes that all cells belong in all categorical values inside of sample response // This expected result assumes that all cells belong in all categorical values inside of sample response
const expectedResult = [ const expectedResult = [
quantile([0.5], obsLayout.col("umap_0").asArray() as NumberArray)[0], quantile([0.5], obsLayout.col("umap_0").asArray())[0],
quantile([0.5], obsLayout.col("umap_1").asArray() as NumberArray)[0], quantile([0.5], obsLayout.col("umap_1").asArray())[0],
]; ];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. centroidResult.forEach((coordinate) => {
centroidResult.forEach((coordinate: any) => {
expect(coordinate).toEqual(expectedResult); expect(coordinate).toEqual(expectedResult);
}); });
}); });
@@ -68,12 +64,11 @@ describe("centroid", () => {
// This expected result assumes that all cells belong in all categorical values inside of sample response // This expected result assumes that all cells belong in all categorical values inside of sample response
const expectedResult = [ const expectedResult = [
quantile([0.5], obsLayout.col("umap_0").asArray() as NumberArray)[0], quantile([0.5], obsLayout.col("umap_0").asArray())[0],
quantile([0.5], obsLayout.col("umap_1").asArray() as NumberArray)[0], quantile([0.5], obsLayout.col("umap_1").asArray())[0],
]; ];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. centroidResult.forEach((coordinate) => {
centroidResult.forEach((coordinate: any) => {
expect(coordinate).toEqual(expectedResult); expect(coordinate).toEqual(expectedResult);
}); });
}); });
@@ -6,8 +6,7 @@ describe("dataframe constructor", () => {
expect(df).toBeDefined(); expect(df).toBeDefined();
expect(df.dims).toEqual([0, 0]); expect(df.dims).toEqual([0, 0]);
expect(df).toHaveLength(0); expect(df).toHaveLength(0);
expect(df.ihasCol(0)).toBeFalsy(); expect(df.icol(0)).not.toBeDefined();
expect(() => df.icol(0)).toThrow(RangeError);
}); });
test("create with default indices", () => { test("create with default indices", () => {
@@ -24,13 +23,6 @@ describe("dataframe constructor", () => {
expect(df.at(2, 1)).toEqual(1); expect(df.at(2, 1)).toEqual(1);
expect(df.iat(0, 0)).toEqual(0); expect(df.iat(0, 0)).toEqual(0);
expect(df.iat(2, 1)).toEqual(1); expect(df.iat(2, 1)).toEqual(1);
expect(Array.from(df.rowIndex.labels())).toEqual(
df.rowIndex.getLabels(df.rowIndex.getOffsets(df.rowIndex.labels()))
);
expect(Array.from(df.colIndex.labels())).toEqual(
df.colIndex.getLabels(df.colIndex.getOffsets(df.colIndex.labels()))
);
}); });
test("create with labelled indices", () => { test("create with labelled indices", () => {
@@ -129,9 +121,7 @@ describe("simple data access", () => {
expect(df.has(3, "foo")).toBeFalsy(); expect(df.has(3, "foo")).toBeFalsy();
expect(df.has(-1, "numbers")).toBeFalsy(); expect(df.has(-1, "numbers")).toBeFalsy();
expect(df.has(-1, -1)).toBeFalsy(); expect(df.has(-1, -1)).toBeFalsy();
expect( expect(df.has(null, null)).toBeFalsy();
df.has(null as unknown as number, null as unknown as number)
).toBeFalsy();
expect(df.has(0, "foo")).toBeFalsy(); expect(df.has(0, "foo")).toBeFalsy();
expect(df.has(99, "numbers")).toBeFalsy(); expect(df.has(99, "numbers")).toBeFalsy();
expect(df.has(99, "foo")).toBeFalsy(); expect(df.has(99, "foo")).toBeFalsy();
@@ -259,12 +249,11 @@ describe("dataframe subsetting", () => {
const df = sourceDf.subset( const df = sourceDf.subset(
null, null,
["int32", "float32"], ["int32", "float32"],
new Dataframe.DenseInt32Index([2, 1]) new Dataframe.DenseInt32Index([3, 2, 1])
); );
expect(df.dims).toEqual([2, 2]);
expect(df.colIndex).toBeInstanceOf(Dataframe.KeyIndex); expect(df.colIndex).toBeInstanceOf(Dataframe.KeyIndex);
expect(df.rowIndex).toBeInstanceOf(Dataframe.DenseInt32Index); expect(df.rowIndex).toBeInstanceOf(Dataframe.DenseInt32Index);
expect(df.at(2, "int32")).toEqual(df.iat(0, 0)); expect(df.at(3, "int32")).toEqual(df.iat(0, 0));
}); });
test("withRowIndex error checks", () => { test("withRowIndex error checks", () => {
@@ -843,7 +832,7 @@ describe("dataframe factories", () => {
}); });
describe("dataframe col", () => { describe("dataframe col", () => {
let df: Dataframe.Dataframe; let df = null;
beforeEach(() => { beforeEach(() => {
df = new Dataframe.Dataframe( df = new Dataframe.Dataframe(
[2, 2], [2, 2],
@@ -860,8 +849,8 @@ describe("dataframe col", () => {
expect(df).toBeDefined(); expect(df).toBeDefined();
expect(df.col("A")).toBe(df.icol(0)); expect(df.col("A")).toBe(df.icol(0));
expect(df.col("B")).toBe(df.icol(1)); expect(df.col("B")).toBe(df.icol(1));
expect(() => df.col("undefined")).toThrow(RangeError); expect(df.col("undefined")).toBeUndefined();
expect(() => df.icol("undefined" as unknown as number)).toThrow(RangeError); expect(df.icol("undefined")).toBeUndefined();
const colA = df.col("A"); const colA = df.col("A");
expect(colA).toBeInstanceOf(Function); expect(colA).toBeInstanceOf(Function);
@@ -915,13 +904,13 @@ describe("dataframe col", () => {
expect(df.col("A").indexOf(true)).toEqual(0); expect(df.col("A").indexOf(true)).toEqual(0);
expect(df.col("A").indexOf(false)).toEqual(1); expect(df.col("A").indexOf(false)).toEqual(1);
expect(df.col("A").indexOf(99)).toBeUndefined(); expect(df.col("A").indexOf(99)).toBeUndefined();
expect(df.col("A").indexOf(undefined as unknown as number)).toBeUndefined(); expect(df.col("A").indexOf(undefined)).toBeUndefined();
expect(df.col("A").indexOf(1)).toBeUndefined(); expect(df.col("A").indexOf(1)).toBeUndefined();
expect(df.col("B").indexOf(1)).toEqual(0); expect(df.col("B").indexOf(1)).toEqual(0);
expect(df.col("B").indexOf(0)).toEqual(1); expect(df.col("B").indexOf(0)).toEqual(1);
expect(df.col("B").indexOf(99)).toBeUndefined(); expect(df.col("B").indexOf(99)).toBeUndefined();
expect(df.col("B").indexOf(undefined as unknown as number)).toBeUndefined(); expect(df.col("B").indexOf(undefined)).toBeUndefined();
expect(df.col("B").indexOf(true)).toBeUndefined(); expect(df.col("B").indexOf(true)).toBeUndefined();
}); });
}); });
@@ -964,7 +953,7 @@ describe("label indexing", () => {
test("offsets", () => { test("offsets", () => {
expect(idx.getOffset(1)).toEqual(1); expect(idx.getOffset(1)).toEqual(1);
expect(idx.getOffsets([1, 3])).toEqual(new Int32Array([1, 3])); expect(idx.getOffsets([1, 3])).toEqual([1, 3]);
}); });
test("subset", () => { test("subset", () => {
@@ -1042,7 +1031,7 @@ describe("label indexing", () => {
false, false,
]) ])
.labels() .labels()
).toEqual([]); ).toEqual(new Int32Array([]));
expect( expect(
idx idx
.isubsetMask([ .isubsetMask([
@@ -1136,14 +1125,16 @@ describe("label indexing", () => {
expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22])); expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22]));
expect(idx.size()).toEqual(5); expect(idx.size()).toEqual(5);
expect(idx.getLabel(0)).toEqual(99); expect(idx.getLabel(0)).toEqual(99);
expect(idx.getLabels(new Int32Array([2, 4]))).toEqual([48, 22]); expect(idx.getLabels(new Int32Array([2, 4]))).toEqual(
new Int32Array([48, 22])
);
expect(idx.getLabels([2, 4])).toEqual([48, 22]); expect(idx.getLabels([2, 4])).toEqual([48, 22]);
}); });
test("offsets", () => { test("offsets", () => {
expect(idx.getOffset(1002)).toEqual(1); expect(idx.getOffset(1002)).toEqual(1);
expect(idx.getOffset(0)).toEqual(3); expect(idx.getOffset(0)).toEqual(3);
expect(idx.getOffsets([0, 48])).toEqual(new Int32Array([3, 2])); expect(idx.getOffsets([0, 48])).toEqual([3, 2]);
}); });
test("subset", () => { test("subset", () => {
@@ -1170,7 +1161,7 @@ describe("label indexing", () => {
); );
expect( expect(
idx.isubsetMask([false, false, false, false, false]).labels() idx.isubsetMask([false, false, false, false, false]).labels()
).toEqual([]); ).toEqual(new Int32Array([]));
expect(idx.isubsetMask([true, true, false, true, true]).labels()).toEqual( expect(idx.isubsetMask([true, true, false, true, true]).labels()).toEqual(
new Int32Array([99, 1002, 0, 22]) new Int32Array([99, 1002, 0, 22])
); );
@@ -1202,7 +1193,6 @@ 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);
}); });
@@ -1272,67 +1262,59 @@ describe("corner cases", () => {
const idx = new Dataframe.IdentityInt32Index(10); const idx = new Dataframe.IdentityInt32Index(10);
expect(idx.getOffset(0)).toBe(0); expect(idx.getOffset(0)).toBe(0);
expect(idx.getOffset(9)).toBe(9); expect(idx.getOffset(9)).toBe(9);
expect(idx.getOffset(10)).toBe(-1); expect(idx.getOffset(10)).toBeUndefined();
expect(idx.getOffset(-1)).toBe(-1); expect(idx.getOffset(-1)).toBeUndefined();
expect(idx.getOffset("sort")).toBe(-1); expect(idx.getOffset("sort")).toBeUndefined();
expect(idx.getOffset("length")).toBe(-1); expect(idx.getOffset("length")).toBeUndefined();
expect(idx.getOffset(true as unknown as string)).toBe(-1); expect(idx.getOffset(true)).toBeUndefined();
expect(idx.getOffset(0.001)).toBe(-1); expect(idx.getOffset(0.001)).toBeUndefined();
expect(idx.getOffset({} as unknown as string)).toBe(-1); expect(idx.getOffset({})).toBeUndefined();
expect(idx.getOffset([] as unknown as string)).toBe(-1); expect(idx.getOffset([])).toBeUndefined();
expect(idx.getOffset(new Float32Array() as unknown as string)).toBe(-1); expect(idx.getOffset(new Float32Array())).toBeUndefined();
expect(idx.getOffset("__proto__")).toBe(-1); expect(idx.getOffset("__proto__")).toBeUndefined();
expect(idx.getLabel(0)).toBe(0); expect(idx.getLabel(0)).toBe(0);
expect(idx.getLabel(9)).toBe(9); expect(idx.getLabel(9)).toBe(9);
expect(idx.getLabel(10)).toBeUndefined(); expect(idx.getLabel(10)).toBeUndefined();
expect(idx.getLabel(-1)).toBeUndefined(); expect(idx.getLabel(-1)).toBeUndefined();
expect(idx.getLabel("sort" as unknown as number)).toBeUndefined(); expect(idx.getLabel("sort")).toBeUndefined();
expect(idx.getLabel("length" as unknown as number)).toBeUndefined(); expect(idx.getLabel("length")).toBeUndefined();
expect(idx.getLabel(true as unknown as number)).toBeUndefined(); expect(idx.getLabel(true)).toBeUndefined();
expect(idx.getLabel(0.001)).toBeUndefined(); expect(idx.getLabel(0.001)).toBeUndefined();
expect(idx.getLabel({} as unknown as number)).toBeUndefined(); expect(idx.getLabel({})).toBeUndefined();
expect(idx.getLabel([] as unknown as number)).toBeUndefined(); expect(idx.getLabel([])).toBeUndefined();
expect( expect(idx.getLabel(new Float32Array())).toBeUndefined();
idx.getLabel(new Float32Array() as unknown as number) expect(idx.getLabel("__proto__")).toBeUndefined();
).toBeUndefined();
expect(idx.getLabel("__proto__" as unknown as number)).toBeUndefined();
}); });
test("dense integer index rejects non-integer labels", () => { test("dense integer index rejects non-integer labels", () => {
const idx = new Dataframe.DenseInt32Index([-10, 0, 3, 9, 10]); const idx = new Dataframe.DenseInt32Index([-10, 0, 3, 9, 10]);
expect(idx.getOffset(-10)).toBe(0);
expect(idx.getOffset(0)).toBe(1); expect(idx.getOffset(0)).toBe(1);
expect(idx.getOffset(3)).toBe(2);
expect(idx.getOffset(9)).toBe(3); expect(idx.getOffset(9)).toBe(3);
expect(idx.getOffset(10)).toBe(4); expect(idx.getOffset(1)).toBeUndefined();
expect(idx.getOffset(11)).toBeUndefined();
expect(idx.getOffset(1)).toBe(-1); expect(idx.getOffset(-1)).toBeUndefined();
expect(idx.getOffset(11)).toBe(-1); expect(idx.getOffset("sort")).toBeUndefined();
expect(idx.getOffset(-1)).toBe(-1); expect(idx.getOffset("length")).toBeUndefined();
expect(idx.getOffset("sort")).toBe(-1); expect(idx.getOffset(true)).toBeUndefined();
expect(idx.getOffset("length")).toBe(-1); expect(idx.getOffset(0.001)).toBeUndefined();
expect(idx.getOffset(true as unknown as string)).toBe(-1); expect(idx.getOffset({})).toBeUndefined();
expect(idx.getOffset(0.001)).toBe(-1); expect(idx.getOffset([])).toBeUndefined();
expect(idx.getOffset({} as unknown as string)).toBe(-1); expect(idx.getOffset(new Float32Array())).toBeUndefined();
expect(idx.getOffset([] as unknown as string)).toBe(-1); expect(idx.getOffset("__proto__")).toBeUndefined();
expect(idx.getOffset(new Float32Array() as unknown as string)).toBe(-1);
expect(idx.getOffset("__proto__")).toBe(-1);
expect(idx.getLabel(0)).toBe(-10); expect(idx.getLabel(0)).toBe(-10);
expect(idx.getLabel(4)).toBe(10); expect(idx.getLabel(4)).toBe(10);
expect(idx.getLabel(10)).toBeUndefined(); expect(idx.getLabel(10)).toBeUndefined();
expect(idx.getLabel(-1)).toBeUndefined(); expect(idx.getLabel(-1)).toBeUndefined();
expect(idx.getLabel("sort" as unknown as number)).toBeUndefined(); expect(idx.getLabel("sort")).toBeUndefined();
expect(idx.getLabel("length" as unknown as number)).toBeUndefined(); expect(idx.getLabel("length")).toBeUndefined();
expect(idx.getLabel(true as unknown as number)).toBeUndefined(); expect(idx.getLabel(true)).toBeUndefined();
expect(idx.getLabel(0.001)).toBeUndefined(); expect(idx.getLabel(0.001)).toBeUndefined();
expect(idx.getLabel({} as unknown as number)).toBeUndefined(); expect(idx.getLabel({})).toBeUndefined();
expect(idx.getLabel([] as unknown as number)).toBeUndefined(); expect(idx.getLabel([])).toBeUndefined();
expect( expect(idx.getLabel(new Float32Array())).toBeUndefined();
idx.getLabel(new Float32Array() as unknown as number) expect(idx.getLabel("__proto__")).toBeUndefined();
).toBeUndefined();
expect(idx.getLabel("__proto__" as unknown as number)).toBeUndefined();
}); });
test("Empty dataframe rejects bogus labels", () => { test("Empty dataframe rejects bogus labels", () => {
@@ -1340,55 +1322,39 @@ describe("corner cases", () => {
expect(df.hasCol("sort")).toBeFalsy(); expect(df.hasCol("sort")).toBeFalsy();
expect(df.hasCol(0)).toBeFalsy(); expect(df.hasCol(0)).toBeFalsy();
expect(df.hasCol(true as unknown as number)).toBeFalsy(); expect(df.hasCol(true)).toBeFalsy();
expect(df.hasCol(false as unknown as number)).toBeFalsy(); expect(df.hasCol(false)).toBeFalsy();
expect(df.hasCol([] as unknown as number)).toBeFalsy(); expect(df.hasCol([])).toBeFalsy();
expect(df.hasCol({} as unknown as number)).toBeFalsy(); expect(df.hasCol({})).toBeFalsy();
expect(df.hasCol(null as unknown as number)).toBeFalsy(); expect(df.hasCol(null)).toBeFalsy();
expect(df.hasCol(undefined as unknown as number)).toBeFalsy(); expect(df.hasCol(undefined)).toBeFalsy();
expect(() => df.col("sort")).toThrow(RangeError); expect(df.col("sort")).toBeUndefined();
expect(() => df.col(0)).toThrow(RangeError); expect(df.col(0)).toBeUndefined();
expect(() => df.col(true as unknown as string)).toThrow(RangeError); expect(df.col(true)).toBeUndefined();
expect(() => df.col(false as unknown as string)).toThrow(RangeError); expect(df.col(false)).toBeUndefined();
expect(() => df.col([] as unknown as string)).toThrow(RangeError); expect(df.col([])).toBeUndefined();
expect(() => df.col({} as unknown as string)).toThrow(RangeError); expect(df.col({})).toBeUndefined();
expect(() => df.col(null as unknown as string)).toThrow(RangeError); expect(df.col(null)).toBeUndefined();
expect(() => df.col(undefined as unknown as string)).toThrow(RangeError); expect(df.col(undefined)).toBeUndefined();
expect(() => df.icol("sort" as unknown as number)).toThrow(RangeError); expect(df.icol("sort")).toBeUndefined();
expect(() => df.icol(0)).toThrow(RangeError); expect(df.icol(0)).toBeUndefined();
expect(() => df.icol(true as unknown as number)).toThrow(RangeError); expect(df.icol(true)).toBeUndefined();
expect(() => df.icol(false as unknown as number)).toThrow(RangeError); expect(df.icol(false)).toBeUndefined();
expect(() => df.icol([] as unknown as number)).toThrow(RangeError); expect(df.icol([])).toBeUndefined();
expect(() => df.icol({} as unknown as number)).toThrow(RangeError); expect(df.icol({})).toBeUndefined();
expect(() => df.icol(null as unknown as number)).toThrow(RangeError); expect(df.icol(null)).toBeUndefined();
expect(() => df.icol(undefined as unknown as number)).toThrow(RangeError); expect(df.icol(undefined)).toBeUndefined();
expect( expect(df.ihas("sort", "length")).toBeFalsy();
df.ihas("sort" as unknown as number, "length" as unknown as number) expect(df.ihas("0", "0")).toBeFalsy();
).toBeFalsy(); expect(df.ihas("", "")).toBeFalsy();
expect( expect(df.ihas(null, null)).toBeFalsy();
df.ihas("0" as unknown as number, "0" as unknown as number) expect(df.ihas(undefined, undefined)).toBeFalsy();
).toBeFalsy(); expect(df.ihas(true, true)).toBeFalsy();
expect( expect(df.ihas([], [])).toBeFalsy();
df.ihas("" as unknown as number, "" as unknown as number) expect(df.ihas({}, {})).toBeFalsy();
).toBeFalsy();
expect(
df.ihas(null as unknown as number, null as unknown as number)
).toBeFalsy();
expect(
df.ihas(undefined as unknown as number, undefined as unknown as number)
).toBeFalsy();
expect(
df.ihas(true as unknown as number, true as unknown as number)
).toBeFalsy();
expect(
df.ihas([] as unknown as number, [] as unknown as number)
).toBeFalsy();
expect(
df.ihas({} as unknown as number, {} as unknown as number)
).toBeFalsy();
}); });
test("Dataframe rejects bogus labels", () => { test("Dataframe rejects bogus labels", () => {
@@ -1405,58 +1371,51 @@ describe("corner cases", () => {
expect(df.hasCol("sort")).toBeFalsy(); expect(df.hasCol("sort")).toBeFalsy();
expect(df.hasCol("__proto__")).toBeFalsy(); expect(df.hasCol("__proto__")).toBeFalsy();
expect(df.hasCol(0)).toBeFalsy(); expect(df.hasCol(0)).toBeFalsy();
expect(df.hasCol(true as unknown as string)).toBeFalsy(); expect(df.hasCol(true)).toBeFalsy();
expect(df.hasCol(false as unknown as string)).toBeFalsy(); expect(df.hasCol(false)).toBeFalsy();
expect(df.hasCol([] as unknown as string)).toBeFalsy(); expect(df.hasCol([])).toBeFalsy();
expect(df.hasCol({} as unknown as string)).toBeFalsy(); expect(df.hasCol({})).toBeFalsy();
expect(df.hasCol(null as unknown as string)).toBeFalsy(); expect(df.hasCol(null)).toBeFalsy();
expect(df.hasCol(undefined as unknown as string)).toBeFalsy(); expect(df.hasCol(undefined)).toBeFalsy();
expect(() => df.col("sort")).toThrow(RangeError); expect(df.col("sort")).toBeUndefined();
expect(() => df.col("__proto__")).toThrow(RangeError); expect(df.col("__proto__")).toBeUndefined();
expect(() => df.col(0)).toThrow(RangeError); expect(df.col(0)).toBeUndefined();
expect(() => df.col(true as unknown as string)).toThrow(RangeError); expect(df.col(true)).toBeUndefined();
expect(() => df.col(false as unknown as string)).toThrow(RangeError); expect(df.col(false)).toBeUndefined();
expect(() => df.col([] as unknown as string)).toThrow(RangeError); expect(df.col([])).toBeUndefined();
expect(() => df.col({} as unknown as string)).toThrow(RangeError); expect(df.col({})).toBeUndefined();
expect(() => df.col(null as unknown as string)).toThrow(RangeError); expect(df.col(null)).toBeUndefined();
expect(() => df.col(undefined as unknown as string)).toThrow(RangeError); expect(df.col(undefined)).toBeUndefined();
expect(() => df.icol("sort" as unknown as number)).toThrow(RangeError); expect(df.icol("sort")).toBeUndefined();
expect(() => df.icol("__proto__" as unknown as number)).toThrow(RangeError); expect(df.icol("__proto__")).toBeUndefined();
expect(() => df.icol(-1)).toThrow(RangeError); expect(df.icol(-1)).toBeUndefined();
expect(() => df.icol(true as unknown as number)).toThrow(RangeError); expect(df.icol(true)).toBeUndefined();
expect(() => df.icol(false as unknown as number)).toThrow(RangeError); expect(df.icol(false)).toBeUndefined();
expect(() => df.icol([] as unknown as number)).toThrow(RangeError); expect(df.icol([])).toBeUndefined();
expect(() => df.icol({} as unknown as number)).toThrow(RangeError); expect(df.icol({})).toBeUndefined();
expect(() => df.icol(null as unknown as number)).toThrow(RangeError); expect(df.icol(null)).toBeUndefined();
expect(() => df.icol(undefined as unknown as number)).toThrow(RangeError); expect(df.icol(undefined)).toBeUndefined();
expect( expect(df.ihas("sort", "length")).toBeFalsy();
df.ihas("sort" as unknown as number, "length" as unknown as number) expect(df.ihas("__proto__", "__proto__")).toBeFalsy();
).toBeFalsy();
expect(
df.ihas(
"__proto__" as unknown as number,
"__proto__" as unknown as number
)
).toBeFalsy();
expect(df.ihas(-1, 0)).toBeFalsy(); expect(df.ihas(-1, 0)).toBeFalsy();
expect(df.ihas("0" as unknown as number, 0)).toBeFalsy(); expect(df.ihas("0", 0)).toBeFalsy();
expect(df.ihas("" as unknown as number, 0)).toBeFalsy(); expect(df.ihas("", 0)).toBeFalsy();
expect(df.ihas(null as unknown as number, 0)).toBeFalsy(); expect(df.ihas(null, 0)).toBeFalsy();
expect(df.ihas(undefined as unknown as number, 0)).toBeFalsy(); expect(df.ihas(undefined, 0)).toBeFalsy();
expect(df.ihas([] as unknown as number, 0)).toBeFalsy(); expect(df.ihas([], 0)).toBeFalsy();
expect(df.ihas({} as unknown as number, 0)).toBeFalsy(); expect(df.ihas({}, 0)).toBeFalsy();
expect(df.ihas(0, -1)).toBeFalsy(); expect(df.ihas(0, -1)).toBeFalsy();
expect(df.ihas(0, "0" as unknown as number)).toBeFalsy(); expect(df.ihas(0, "0")).toBeFalsy();
expect(df.ihas(0, "" as unknown as number)).toBeFalsy(); expect(df.ihas(0, "")).toBeFalsy();
expect(df.ihas(0, null as unknown as number)).toBeFalsy(); expect(df.ihas(0, null)).toBeFalsy();
expect(df.ihas(0, undefined as unknown as number)).toBeFalsy(); expect(df.ihas(0, undefined)).toBeFalsy();
expect(df.ihas(0, [] as unknown as number)).toBeFalsy(); expect(df.ihas(0, [])).toBeFalsy();
expect(df.ihas(0, {} as unknown as number)).toBeFalsy(); expect(df.ihas(0, {})).toBeFalsy();
expect(df.has("sort", "length")).toBeFalsy(); expect(df.has("sort", "length")).toBeFalsy();
expect(df.has("length", "sort")).toBeFalsy(); expect(df.has("length", "sort")).toBeFalsy();
@@ -1465,17 +1424,17 @@ describe("corner cases", () => {
expect(df.has(-1, "A")).toBeFalsy(); expect(df.has(-1, "A")).toBeFalsy();
expect(df.has("0", "A")).toBeFalsy(); expect(df.has("0", "A")).toBeFalsy();
expect(df.has("", "A")).toBeFalsy(); expect(df.has("", "A")).toBeFalsy();
expect(df.has(null as unknown as string, "A")).toBeFalsy(); expect(df.has(null, "A")).toBeFalsy();
expect(df.has(undefined as unknown as string, "A")).toBeFalsy(); expect(df.has(undefined, "A")).toBeFalsy();
expect(df.has([] as unknown as string, "A")).toBeFalsy(); expect(df.has([], "A")).toBeFalsy();
expect(df.has({} as unknown as string, "A")).toBeFalsy(); expect(df.has({}, "A")).toBeFalsy();
expect(df.has(0, -1)).toBeFalsy(); expect(df.has(0, -1)).toBeFalsy();
expect(df.has(0, "0")).toBeFalsy(); expect(df.has(0, "0")).toBeFalsy();
expect(df.has(0, "")).toBeFalsy(); expect(df.has(0, "")).toBeFalsy();
expect(df.has(0, null as unknown as string)).toBeFalsy(); expect(df.has(0, null)).toBeFalsy();
expect(df.has(0, undefined as unknown as string)).toBeFalsy(); expect(df.has(0, undefined)).toBeFalsy();
expect(df.has(0, [] as unknown as string)).toBeFalsy(); expect(df.has(0, [])).toBeFalsy();
expect(df.has(0, {} as unknown as string)).toBeFalsy(); expect(df.has(0, {})).toBeFalsy();
}); });
}); });
@@ -9,7 +9,7 @@ describe("Dataframe column histogram", () => {
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
const h1 = df.col("cat").histogramCategoricalBy(df.col("name")); const h1 = df.col("cat").histogram(df.col("name"));
expect(h1).toMatchObject( expect(h1).toMatchObject(
new Map([ new Map([
["n1", new Map([["c1", 1]])], ["n1", new Map([["c1", 1]])],
@@ -18,9 +18,7 @@ describe("Dataframe column histogram", () => {
]) ])
); );
// memoized? // memoized?
expect(df.col("cat").histogramCategoricalBy(df.col("name"))).toMatchObject( expect(df.col("cat").histogram(df.col("name"))).toMatchObject(h1);
h1
);
}); });
test("continuous by categorical", () => { test("continuous by categorical", () => {
@@ -31,7 +29,7 @@ describe("Dataframe column histogram", () => {
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
const h1 = df.col("value").histogramContinuousBy(3, [0, 2], df.col("name")); const h1 = df.col("value").histogram(3, [0, 2], df.col("name"));
expect(h1).toMatchObject( expect(h1).toMatchObject(
new Map([ new Map([
["n1", [1, 0, 0]], ["n1", [1, 0, 0]],
@@ -40,9 +38,9 @@ describe("Dataframe column histogram", () => {
]) ])
); );
// memoized? // memoized?
expect( expect(df.col("value").histogram(3, [0, 2], df.col("name"))).toMatchObject(
df.col("value").histogramContinuousBy(3, [0, 2], df.col("name")) h1
).toMatchObject(h1); );
}); });
test("categorical", () => { test("categorical", () => {
@@ -53,7 +51,7 @@ describe("Dataframe column histogram", () => {
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
const h1 = df.col("cat").histogramCategorical(); const h1 = df.col("cat").histogram();
expect(h1).toMatchObject( expect(h1).toMatchObject(
new Map([ new Map([
["c1", 1], ["c1", 1],
@@ -62,7 +60,7 @@ describe("Dataframe column histogram", () => {
]) ])
); );
// memoized? // memoized?
expect(df.col("cat").histogramCategorical()).toMatchObject(h1); expect(df.col("value").histogram(3, [0, 2])).toMatchObject(h1);
}); });
test("continuous", () => { test("continuous", () => {
@@ -73,10 +71,10 @@ describe("Dataframe column histogram", () => {
new Dataframe.KeyIndex(["name", "cat", "value"]) new Dataframe.KeyIndex(["name", "cat", "value"])
); );
const h1 = df.col("value").histogramContinuous(3, [0, 2]); const h1 = df.col("value").histogram(3, [0, 2]);
expect(h1).toMatchObject([1, 1, 1]); expect(h1).toMatchObject([1, 1, 1]);
// memoized? // memoized?
expect(df.col("value").histogramContinuous(3, [0, 2])).toMatchObject(h1); expect(df.col("value").histogram(3, [0, 2])).toMatchObject(h1);
}); });
test("continuous thesholds correct", () => { test("continuous thesholds correct", () => {
@@ -86,10 +84,10 @@ describe("Dataframe column histogram", () => {
[new Int32Array(vals), new Float32Array(vals)] [new Int32Array(vals), new Float32Array(vals)]
); );
expect(df.col(0).histogramContinuous(5, [0, 100])).toEqual([5, 1, 0, 0, 2]); expect(df.col(0).histogram(5, [0, 100])).toEqual([5, 1, 0, 0, 2]);
expect(df.col(1).histogramContinuous(5, [0, 100])).toEqual([5, 1, 0, 0, 2]); expect(df.col(1).histogram(5, [0, 100])).toEqual([5, 1, 0, 0, 2]);
expect(df.col(0).histogramContinuous(2, [0, 10])).toEqual([2, 2]); expect(df.col(0).histogram(2, [0, 10])).toEqual([2, 2]);
expect(df.col(0).histogramContinuous(10, [0, 100])).toEqual([ expect(df.col(0).histogram(10, [0, 100])).toEqual([
3, 2, 1, 0, 0, 0, 0, 0, 0, 2, 3, 2, 1, 0, 0, 0, 0, 0, 0, 2,
]); ]);
}); });
@@ -1,14 +1,13 @@
import * as Dataframe from "../../../src/util/dataframe"; import * as Dataframe from "../../../src/util/dataframe";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function float32Conversion(f) {
function float32Conversion(f: any) {
return new Float32Array([f])[0]; return new Float32Array([f])[0];
} }
describe("Dataframe column summary", () => { describe("Dataframe column summary", () => {
test("empty column test", () => { test("empty column test", () => {
const df = Dataframe.Dataframe.create([0, 1], [[]]); const df = Dataframe.Dataframe.create([0, 1], [[]]);
const summary = df.icol(0).summarizeCategorical(); const summary = df.icol(0).summarize();
expect(summary).toEqual( expect(summary).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
@@ -41,7 +40,7 @@ describe("Dataframe column summary", () => {
]) ])
); );
expect(df.icol(0).summarizeCategorical()).toEqual( expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: ["n1"], categories: ["n1"],
@@ -49,7 +48,7 @@ describe("Dataframe column summary", () => {
numCategories: 1, numCategories: 1,
}) })
); );
expect(df.icol(1).summarizeCategorical()).toEqual( expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: ["hi"], categories: ["hi"],
@@ -57,7 +56,7 @@ describe("Dataframe column summary", () => {
numCategories: 1, numCategories: 1,
}) })
); );
expect(df.icol(2).summarizeCategorical()).toEqual( expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: [true], categories: [true],
@@ -65,7 +64,7 @@ describe("Dataframe column summary", () => {
numCategories: 1, numCategories: 1,
}) })
); );
expect(df.icol(3).summarizeContinuous()).toEqual( expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: false, categorical: false,
min: float32Conversion(39.3), min: float32Conversion(39.3),
@@ -75,7 +74,7 @@ describe("Dataframe column summary", () => {
pinf: 0, pinf: 0,
}) })
); );
expect(df.icol(4).summarizeContinuous()).toEqual( expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: false, categorical: false,
min: 99, min: 99,
@@ -85,7 +84,7 @@ describe("Dataframe column summary", () => {
pinf: 0, pinf: 0,
}) })
); );
expect(df.icol(5).summarizeCategorical()).toEqual( expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: [1], categories: [1],
@@ -117,7 +116,7 @@ describe("Dataframe column summary", () => {
]) ])
); );
expect(df.icol(0).summarizeCategorical()).toEqual( expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]), categories: expect.arrayContaining(["n0", "n1", "n2"]),
@@ -129,7 +128,7 @@ describe("Dataframe column summary", () => {
numCategories: 3, numCategories: 3,
}) })
); );
expect(df.icol(1).summarizeCategorical()).toEqual( expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["hi", "bye"]), categories: expect.arrayContaining(["hi", "bye"]),
@@ -140,7 +139,7 @@ describe("Dataframe column summary", () => {
numCategories: 2, numCategories: 2,
}) })
); );
expect(df.icol(2).summarizeCategorical()).toEqual( expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([true, false]), categories: expect.arrayContaining([true, false]),
@@ -151,7 +150,7 @@ describe("Dataframe column summary", () => {
numCategories: 2, numCategories: 2,
}) })
); );
expect(df.icol(3).summarizeContinuous()).toEqual( expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: false, categorical: false,
min: 0, min: 0,
@@ -161,7 +160,7 @@ describe("Dataframe column summary", () => {
pinf: 0, pinf: 0,
}) })
); );
expect(df.icol(4).summarizeContinuous()).toEqual( expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: false, categorical: false,
min: 99, min: 99,
@@ -171,11 +170,10 @@ describe("Dataframe column summary", () => {
pinf: 0, pinf: 0,
}) })
); );
expect(df.icol(5).summarizeCategorical()).toEqual( expect(df.icol(5).summarize()).toEqual(
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],
@@ -213,7 +211,7 @@ describe("Dataframe column summary", () => {
]) ])
); );
expect(df.icol(0).summarizeCategorical()).toEqual( expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]), categories: expect.arrayContaining(["n0", "n1", "n2"]),
@@ -225,7 +223,7 @@ describe("Dataframe column summary", () => {
numCategories: 3, numCategories: 3,
}) })
); );
expect(df.icol(1).summarizeCategorical()).toEqual( expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining(["hi", "bye"]), categories: expect.arrayContaining(["hi", "bye"]),
@@ -236,7 +234,7 @@ describe("Dataframe column summary", () => {
numCategories: 2, numCategories: 2,
}) })
); );
expect(df.icol(2).summarizeCategorical()).toEqual( expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([true, false]), categories: expect.arrayContaining([true, false]),
@@ -247,7 +245,7 @@ describe("Dataframe column summary", () => {
numCategories: 2, numCategories: 2,
}) })
); );
expect(df.icol(3).summarizeContinuous()).toEqual( expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: false, categorical: false,
min: float32Conversion(39.3), min: float32Conversion(39.3),
@@ -257,7 +255,7 @@ describe("Dataframe column summary", () => {
pinf: 1, pinf: 1,
}) })
); );
expect(df.icol(4).summarizeContinuous()).toEqual( expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: false, categorical: false,
min: 99, min: 99,
@@ -267,11 +265,10 @@ describe("Dataframe column summary", () => {
pinf: 0, pinf: 0,
}) })
); );
expect(df.icol(5).summarizeCategorical()).toEqual( expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({ expect.objectContaining({
categorical: true, categorical: true,
categories: expect.arrayContaining([1, false, "0"]), categories: expect.arrayContaining([1, false, "0"]),
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
categoryCounts: new Map([ categoryCounts: new Map([
[1, 1], [1, 1],
[false, 1], [false, 1],
@@ -1,8 +1,7 @@
import PromiseLimit from "../../src/util/promiseLimit"; import PromiseLimit from "../../src/util/promiseLimit";
import { range } from "../../src/util/range"; import { range } from "../../src/util/range";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. const delay = (t) => new Promise((resolve) => setTimeout(resolve, t));
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 () => {
@@ -52,7 +51,7 @@ describe("PromiseLimit", () => {
running -= 1; running -= 1;
}; };
await Promise.all(range(10).map(() => plimit.add(() => callback()))); await Promise.all(range(10).map((i) => plimit.add(() => callback(i))));
expect(maxRunning).toEqual(2); expect(maxRunning).toEqual(2);
}); });
@@ -19,11 +19,7 @@ describe("quantile", () => {
test("multi q", () => { test("multi q", () => {
const arr = new Float32Array([9, 3, 5, 6, 0]); const arr = new Float32Array([9, 3, 5, 6, 0]);
expect(quantile([0, 0.25, 0.5, 0.75, 1.0], arr)).toMatchObject([ expect(quantile([0, 0.25, 0.5, 0.75, 1.0], arr)).toMatchObject([
0, 0, 3, 5, 6, 9,
3,
5,
6,
9,
]); ]);
}); });
}); });
@@ -95,7 +95,6 @@ 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])));
} }
}); });
@@ -113,7 +112,6 @@ 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])));
} }
}); });
@@ -124,8 +122,7 @@ describe("categorical color helpers", () => {
Array.from(schema.annotations.obsByName.categoricalColumn.categories) Array.from(schema.annotations.obsByName.categoricalColumn.categories)
); );
const userDefinedColorTable = { const userDefinedColorTable = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. categoricalColumn: shuffleCats.reduce((acc, label) => {
categoricalColumn: shuffleCats.reduce((acc: any, label: any) => {
acc[label] = randRGBColor(); acc[label] = randRGBColor();
return acc; return acc;
}, {}), }, {}),
@@ -139,14 +136,12 @@ 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()
); );
} }
@@ -159,38 +154,31 @@ TODO:
2. user defined colors 2. user defined colors
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function indexSchema(schema) {
function indexSchema(schema: any) {
schema.annotations.obsByName = Object.fromEntries( schema.annotations.obsByName = Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. schema.annotations?.obs?.columns?.map((v) => [v.name, v]) ?? []
schema.annotations?.obs?.columns?.map((v: any) => [v.name, v]) ?? []
); );
schema.annotations.varByName = Object.fromEntries( schema.annotations.varByName = Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. schema.annotations?.var?.columns?.map((v) => [v.name, v]) ?? []
schema.annotations?.var?.columns?.map((v: any) => [v.name, v]) ?? []
); );
schema.layout.obsByName = Object.fromEntries( schema.layout.obsByName = Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. schema.layout?.obs?.map((v) => [v.name, v]) ?? []
schema.layout?.obs?.map((v: any) => [v.name, v]) ?? []
); );
schema.layout.varByName = Object.fromEntries( schema.layout.varByName = Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. schema.layout?.var?.map((v) => [v.name, v]) ?? []
schema.layout?.var?.map((v: any) => [v.name, v]) ?? []
); );
return schema; return schema;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function makeScale(rgb) {
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
})`; })`;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function shuffle(array) {
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]];
@@ -5,7 +5,6 @@ import zip from "lodash.zip";
import _ from "lodash"; import _ from "lodash";
import { flatbuffers } from "flatbuffers"; import { flatbuffers } from "flatbuffers";
import { NetEncoding } from "../../../src/util/stateManager/matrix_generated"; import { NetEncoding } from "../../../src/util/stateManager/matrix_generated";
import { RawSchema } from "../../../src/common/types/schema";
/* /*
test data mocking REST 0.2 API responses. Used in several tests. test data mocking REST 0.2 API responses. Used in several tests.
@@ -30,7 +29,7 @@ const aConfigResponse = {
}, },
}; };
const aSchemaResponse: { schema: RawSchema } = { const aSchemaResponse = {
schema: { schema: {
dataframe: { dataframe: {
nObs, nObs,
@@ -41,30 +40,28 @@ const aSchemaResponse: { schema: RawSchema } = {
obs: { obs: {
index: "name", index: "name",
columns: [ columns: [
{ name: "name", type: "string", writable: false }, { name: "name", type: "string" },
{ name: "field1", type: "int32", writable: false }, { name: "field1", type: "int32" },
{ name: "field2", type: "float32", writable: false }, { name: "field2", type: "float32" },
{ name: "field3", type: "boolean", writable: false }, { name: "field3", type: "boolean" },
{ {
name: "field4", name: "field4",
type: "categorical", type: "categorical",
categories: field4Categories, categories: field4Categories,
writable: false,
}, },
], ],
}, },
var: { var: {
index: "name", index: "name",
columns: [ columns: [
{ name: "name", type: "string", writable: false }, { name: "name", type: "string" },
{ name: "fieldA", type: "int32", writable: false }, { name: "fieldA", type: "int32" },
{ name: "fieldB", type: "float32", writable: false }, { name: "fieldB", type: "float32" },
{ name: "fieldC", type: "boolean", writable: false }, { name: "fieldC", type: "boolean" },
{ {
name: "fieldD", name: "fieldD",
type: "categorical", type: "categorical",
categories: fieldDCategories, categories: fieldDCategories,
writable: false,
}, },
], ],
}, },
@@ -78,7 +75,6 @@ const aSchemaResponse: { schema: RawSchema } = {
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) => [
@@ -95,7 +91,6 @@ 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) => [
@@ -110,11 +105,8 @@ const anAnnotationsVarJSONResponse = {
.value(), .value(),
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function encodeTypedArray(builder, uType, uData) {
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);
@@ -122,8 +114,7 @@ function encodeTypedArray(builder: any, uType: any, uData: any) {
return builder.endObject(); return builder.endObject();
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function encodeMatrix(columns, colIndex = undefined) {
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
@@ -132,7 +123,6 @@ function encodeMatrix(columns: any, 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) => {
@@ -182,13 +172,11 @@ function encodeMatrix(columns: any, 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);
})(); })();
@@ -197,13 +185,11 @@ const aLayoutFBSResponse = (() => {
new Float32Array(nObs).fill(Math.random()), new Float32Array(nObs).fill(Math.random()),
new Float32Array(nObs).fill(Math.random()), new Float32Array(nObs).fill(Math.random()),
]; ];
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
return encodeMatrix(coords, ["umap_0", "umap_1"]); return encodeMatrix(coords, ["umap_0", "umap_1"]);
})(); })();
const aDataObsResponse = { const aDataObsResponse = {
var: [2, 4, 29], var: [2, 4, 29],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
obs: _() obs: _()
.range(nObs) .range(nObs)
.map((idx) => [idx, Math.random(), Math.random(), Math.random()]) .map((idx) => [idx, Math.random(), Math.random(), Math.random()])
@@ -126,8 +126,7 @@ const someData = [
}, },
]; ];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let payments = null;
let payments: any = null;
beforeEach(() => { beforeEach(() => {
payments = new Crossfilter(someData); payments = new Crossfilter(someData);
}); });
@@ -139,13 +138,7 @@ describe("ImmutableTypedCrossfilter", () => {
expect(payments.all()).toEqual(someData); expect(payments.all()).toEqual(someData);
const p = payments const p = payments
.addDimension( .addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
"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);
@@ -165,8 +158,7 @@ describe("ImmutableTypedCrossfilter", () => {
const p2 = payments.addDimension( const p2 = payments.addDimension(
"quantity", "quantity",
"scalar", "scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (i, data) => data[i].quantity,
(i: any, data: any) => data[i].quantity,
Int32Array Int32Array
); );
@@ -183,22 +175,10 @@ describe("ImmutableTypedCrossfilter", () => {
test("select all and none", () => { test("select all and none", () => {
let p = payments let p = payments
.addDimension( .addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
"quantity", .addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
"scalar", .addDimension("total", "scalar", (i, d) => d[i].total, Float32Array)
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. .addDimension("type", "enum", (i, d) => d[i].type);
(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 */
@@ -250,24 +230,11 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("scalar dimension", () => { describe("scalar dimension", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let p;
let p: any;
beforeEach(() => { beforeEach(() => {
p = payments p = payments
.addDimension( .addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
"quantity", .addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
"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" });
}); });
@@ -310,11 +277,9 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("enum dimension", () => { describe("enum dimension", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let p;
let p: any;
beforeEach(() => { beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. p = payments.addDimension("type", "enum", (i, d) => d[i].type);
p = payments.addDimension("type", "enum", (i: any, d: any) => d[i].type);
}); });
test("all", () => { test("all", () => {
@@ -352,8 +317,7 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("spatial dimension", () => { describe("spatial dimension", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let p;
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]);
@@ -442,22 +406,14 @@ describe("ImmutableTypedCrossfilter", () => {
}); });
describe("non-finite scalars", () => { describe("non-finite scalars", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. let p;
let p: any;
beforeEach(() => { beforeEach(() => {
p = payments p = payments
.addDimension( .addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
"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",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (i, d) => d[i].nonFinite,
(i: any, d: any) => d[i].nonFinite,
Float32Array Float32Array
) )
.select("quantity", { mode: "all" }); .select("quantity", { mode: "all" });
@@ -151,9 +151,9 @@ describe("intersection", () => {
[1, 2], [1, 2],
[6, 9], [6, 9],
]); ]);
expect( expect(PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])).toEqual(
PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]]) [[1363, 2638]]
).toEqual([[1363, 2638]]); );
expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([ expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([
[1, 2], [1, 2],
]); ]);
@@ -15,8 +15,7 @@ paths for:
const pInf = Number.POSITIVE_INFINITY; const pInf = Number.POSITIVE_INFINITY;
const nInf = Number.NEGATIVE_INFINITY; const nInf = Number.NEGATIVE_INFINITY;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function fillRange(arr, start = 0) {
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;
@@ -24,8 +23,7 @@ function fillRange(arr: any, start = 0) {
return larr; return larr;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function fillRand(arr) {
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();
} }
@@ -50,22 +48,16 @@ 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()
); );
}) })
@@ -138,27 +130,22 @@ 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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. index1.sort((a, b) => source1[a] - source1[b])
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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. index2.sort((a, b) => source1[a] - source1[b])
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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. index3.sort((a, b) => source1[a] - source1[b])
index3.sort((a: any, b: any) => source1[a] - source1[b])
); );
}) })
); );
+1 -1
View File
@@ -11,13 +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-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { 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",
+1 -1
View File
@@ -10,13 +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-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { 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",
+4 -40
View File
@@ -1,10 +1,8 @@
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
module.exports = { module.exports = {
root: true, root: true,
parser: "@typescript-eslint/parser", parser: "babel-eslint",
extends: [ extends: [
"airbnb-typescript", "airbnb",
"plugin:@typescript-eslint/recommended",
"plugin:eslint-comments/recommended", "plugin:eslint-comments/recommended",
"plugin:@blueprintjs/recommended", "plugin:@blueprintjs/recommended",
"plugin:compat/recommended", "plugin:compat/recommended",
@@ -37,50 +35,18 @@ module.exports = {
jsx: true, jsx: true,
generators: true, generators: true,
}, },
// (thuang): Pairing with `tsconfigRootDir`, which points to the directory
// of eslint.js
project: "../../tsconfig.json",
tsconfigRootDir: __dirname,
}, },
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: ["*"] }],
@@ -88,7 +54,6 @@ 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",
@@ -107,9 +72,9 @@ module.exports = {
}, },
overrides: [ overrides: [
{ {
files: ["**/*.test.ts"], files: ["**/*.test.js"],
env: { env: {
jest: true, // now **/*.test.ts files' env has both es6 *and* jest jest: true, // now **/*.test.js 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"]
@@ -124,4 +89,3 @@ module.exports = {
}, },
], ],
}; };
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
@@ -1,4 +1,4 @@
module.exports = { module.exports = {
"*.{js,ts,jsx,tsx}": "eslint --fix", "*.js": "eslint --fix",
"**/*": "prettier --write --ignore-unknown", "**/*": "prettier --write --ignore-unknown",
}; };
@@ -1,10 +1,6 @@
/* 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) => {
@@ -54,5 +50,4 @@ 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,34 +37,36 @@
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 style="display: flex; justify-content: space-around; margin-top: 16px"> <div
style="display: flex; justify-content: space-around; margin-top: 16px;"
>
<a <a
href="https://www.google.com/chrome/?hl=en%22" href="https://www.google.com/chrome/?hl=en%22"
aria-label="Download Google Chrome" aria-label="Download Google Chrome"
> >
<img <img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/chrome.png" src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/chrome.png"
style="width: 80px; height: 80px" style="width: 80px; height: 80px;"
/> />
<div>Chrome &gt; 60</div> <div>Chrome &gt; 60</div>
</a> </a>
<a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox"> <a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox">
<img <img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/firefox.png" src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/firefox.png"
style="width: 80px; height: 80px" style="width: 80px; height: 80px;"
/> />
<div>Firefox ≥ 60</div> <div>Firefox ≥ 60</div>
</a> </a>
<a href="//www.microsoft.com/edge" aria-label="Download Edge"> <a href="//www.microsoft.com/edge" aria-label="Download Edge">
<img <img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png" src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png"
style="width: 80px; height: 80px" style="width: 80px; height: 80px;"
/> />
<div>Edge ≥ 79</div> <div>Edge ≥ 79</div>
</a> </a>
@@ -1,23 +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 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");
@@ -33,7 +23,7 @@ const devConfig = {
module: { module: {
rules: [ rules: [
{ {
test: /\.(ts|js)x?$/, test: /\.jsx?$/,
loader: "babel-loader", loader: "babel-loader",
options: babelOptions, options: babelOptions,
}, },
@@ -93,4 +83,3 @@ const devConfig = {
}; };
module.exports = merge(sharedConfig, devConfig); module.exports = merge(sharedConfig, devConfig);
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
@@ -1,31 +1,18 @@
// 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");
@@ -51,7 +38,7 @@ const prodConfig = {
module: { module: {
rules: [ rules: [
{ {
test: /\.(ts|js)x?$/, test: /\.jsx?$/,
loader: "babel-loader", loader: "babel-loader",
options: babelOptions, options: babelOptions,
}, },
@@ -1,13 +1,8 @@
/* 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 @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS. // eslint-disable-next-line @blueprintjs/classes-constants -- incorrect match
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");
@@ -34,9 +29,6 @@ module.exports = {
path: path.resolve("build"), path: path.resolve("build"),
publicPath, publicPath,
}, },
resolve: {
extensions: [".ts", ".tsx", "..."],
},
module: { module: {
rules: [ rules: [
{ {
@@ -80,4 +72,3 @@ module.exports = {
}), }),
], ],
}; };
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
+1389 -2545
View File
File diff suppressed because it is too large Load Diff
+15 -40
View File
@@ -8,14 +8,13 @@
"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.ts", "e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js",
"e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.ts", "e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.js",
"e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts", "e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js",
"fmt": "eslint --fix src __tests__", "fmt": "eslint --fix src __tests__",
"lint": "eslint src __tests__ & npm run type-check", "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",
"test": "jest --testPathIgnorePatterns e2e", "test": "jest --testPathIgnorePatterns e2e"
"type-check": "tsc --noEmit"
}, },
"engineStrict": true, "engineStrict": true,
"engines": { "engines": {
@@ -89,38 +88,10 @@
"@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",
@@ -132,7 +103,7 @@
"codecov": "^3.7.1", "codecov": "^3.7.1",
"css-loader": "^5.2.4", "css-loader": "^5.2.4",
"eslint": "^7.24.0", "eslint": "^7.24.0",
"eslint-config-airbnb-typescript": "^12.3.1", "eslint-config-airbnb": "^18.2.0",
"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",
@@ -172,7 +143,6 @@
"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",
"terser-webpack-plugin": "^5.1.1", "terser-webpack-plugin": "^5.1.1",
"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",
@@ -180,10 +150,10 @@
}, },
"jest": { "jest": {
"testMatch": [ "testMatch": [
"**/__tests__/**/?(*.)(spec|test).ts?(x)" "**/__tests__/**/?(*.)(spec|test).js?(x)"
], ],
"setupFiles": [ "setupFiles": [
"./__tests__/setupMissingGlobals.ts" "./__tests__/setupMissingGlobals.js"
], ],
"coverageDirectory": "./coverage/", "coverageDirectory": "./coverage/",
"collectCoverage": true "collectCoverage": true
@@ -193,8 +163,7 @@
"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",
@@ -216,6 +185,12 @@
"loose": true "loose": true
} }
], ],
[
"@babel/plugin-proposal-private-property-in-object",
{
"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",
+455
View File
@@ -0,0 +1,455 @@
/*
Action creators for user annotation
*/
import difference from "lodash.difference";
import pako from "pako";
import * as globals from "../globals";
import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
const { isUserAnnotation } = AnnotationsHelpers;
export const annotationCreateCategoryAction =
(newCategoryName, categoryToDuplicate) => async (dispatch, getState) => {
/*
Add a new user-created category to the obs annotations.
Arguments:
newCategoryName - string name for the category.
categoryToDuplicate - obs category to use for initial values, or null.
*/
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
const { schema } = prevAnnoMatrix;
/* name must be a string, non-zero length */
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
throw new Error("user annotations require string name");
/* ensure the name isn't already in use! */
if (schema.annotations.obsByName[newCategoryName])
throw new Error("name collision on annotation category create");
let initialValue;
let newSchema;
let ctor;
if (categoryToDuplicate) {
/* if we are duplicating a category, retrieve it */
const catDupSchema = schema.annotations.obsByName[categoryToDuplicate];
const catDupType = catDupSchema?.type;
if (catDupType !== "string" && catDupType !== "categorical")
throw new Error(
"categoryToDuplicate does not exist or has invalid type"
);
const catToDupDf = await prevAnnoMatrix
.base()
.fetch("obs", categoryToDuplicate);
const col = catToDupDf.col(categoryToDuplicate);
initialValue = col.asArray();
const { categories } = col.summarizeCategorical();
// all user-created annotations must have the unassigned category
if (!categories.includes(globals.unassignedCategoryLabel)) {
categories.push(globals.unassignedCategoryLabel);
}
ctor = initialValue.constructor;
newSchema = {
...catDupSchema,
name: newCategoryName,
categories,
writable: true,
};
} else {
/* else assign to the standard default value */
initialValue = globals.unassignedCategoryLabel;
ctor = Array;
newSchema = {
name: newCategoryName,
type: "categorical",
categories: [globals.unassignedCategoryLabel],
writable: true,
};
}
const obsCrossfilter = prevObsCrossfilter.addObsColumn(
newSchema,
ctor,
initialValue
);
dispatch({
type: "annotation: create category",
data: newCategoryName,
categoryToDuplicate,
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
});
};
export const annotationRenameCategoryAction =
(oldCategoryName, newCategoryName) => (dispatch, getState) => {
/*
Rename a user-created annotation category
*/
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, oldCategoryName))
throw new Error("not a user annotation");
/* name must be a string, non-zero length */
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
throw new Error("user annotations require string name");
if (oldCategoryName === newCategoryName) return;
const obsCrossfilter = prevObsCrossfilter.renameObsColumn(
oldCategoryName,
newCategoryName
);
dispatch({
type: "annotation: category edited",
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
metadataField: oldCategoryName,
newCategoryText: newCategoryName,
data: newCategoryName,
});
};
export const annotationDeleteCategoryAction =
(categoryName) => (dispatch, getState) => {
/*
Delete a user-created category
*/
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
const obsCrossfilter = prevObsCrossfilter.dropObsColumn(categoryName);
dispatch({
type: "annotation: delete category",
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
metadataField: categoryName,
});
};
export const annotationCreateLabelInCategory =
(categoryName, labelName, assignSelected) => async (dispatch, getState) => {
/*
Add a new label to a user-defined category. If assignSelected is true, assign
the label to all currently selected cells.
*/
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
let obsCrossfilter = prevObsCrossfilter.addObsAnnoCategory(
categoryName,
labelName
);
if (assignSelected) {
obsCrossfilter = await obsCrossfilter.setObsColumnValues(
categoryName,
prevObsCrossfilter.allSelectedLabels(),
labelName
);
}
dispatch({
type: "annotation: add new label to category",
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
metadataField: categoryName,
newLabelText: labelName,
assignSelectedCells: assignSelected,
});
};
export const annotationDeleteLabelFromCategory =
(categoryName, labelName) => async (dispatch, getState) => {
/*
delete a label from a user-defined category
*/
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
const obsCrossfilter = await prevObsCrossfilter.removeObsAnnoCategory(
categoryName,
labelName,
globals.unassignedCategoryLabel
);
dispatch({
type: "annotation: delete label",
metadataField: categoryName,
label: labelName,
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
});
};
export const annotationRenameLabelInCategory =
(categoryName, oldLabelName, newLabelName) => async (dispatch, getState) => {
/*
label name change
*/
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
let obsCrossfilter = await prevObsCrossfilter.resetObsColumnValues(
categoryName,
oldLabelName,
newLabelName
);
obsCrossfilter = await obsCrossfilter.removeObsAnnoCategory(
categoryName,
oldLabelName,
globals.unassignedCategoryLabel
);
dispatch({
type: "annotation: label edited",
editedLabel: newLabelName,
metadataField: categoryName,
label: oldLabelName,
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
});
};
export const annotationLabelCurrentSelection =
(categoryName, labelName) => async (dispatch, getState) => {
/*
set the label on all currently selected
*/
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
const obsCrossfilter = await prevObsCrossfilter.setObsColumnValues(
categoryName,
prevObsCrossfilter.allSelectedLabels(),
labelName
);
dispatch({
type: "annotation: label current cell selection",
metadataField: categoryName,
label: labelName,
obsCrossfilter,
annoMatrix: obsCrossfilter.annoMatrix,
});
};
function writableAnnotations(annoMatrix) {
return annoMatrix.schema.annotations.obs.columns
.filter((s) => s.writable)
.map((s) => s.name);
}
export const needToSaveObsAnnotations = (annoMatrix, lastSavedAnnoMatrix) => {
/*
Return true if there are LIKELY user-defined annotation modifications between the two
annoMatrices. Technically not an action creator, but intimately intertwined
with the save process.
Two conditions will trigger a need to save:
* the collection of user-defined columns have changed
* the contents of the user-defined columns have change
*/
annoMatrix = annoMatrix.base();
// if the annoMatrix hasn't changed, we are guaranteed no changes to the matrix schema or contents.
if (annoMatrix === lastSavedAnnoMatrix) return false;
// if the schema has changed, we need to save
const currentWritable = writableAnnotations(annoMatrix);
if (difference(currentWritable, writableAnnotations(lastSavedAnnoMatrix))) {
return true;
}
// no schema changes; check for change in contents
return currentWritable.some(
(col) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col)
);
};
export const saveObsAnnotationsAction = () => async (dispatch, getState) => {
/*
Save the user-created obs annotations IF any have changed.
*/
const state = getState();
const { annotations, autosave } = state;
const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations;
const { lastSavedAnnoMatrix, saveInProgress } = autosave;
const annoMatrix = state.annoMatrix.base();
if (saveInProgress || annoMatrix === lastSavedAnnoMatrix) return;
if (!needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix)) {
dispatch({
type: "writable obs annotations - save complete",
lastSavedAnnoMatrix: annoMatrix,
});
return;
}
/*
Else, we really do need to save
*/
dispatch({
type: "writable obs annotations - save started",
});
const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix));
const matrix = MatrixFBS.encodeMatrixFBS(df);
const compressedMatrix = pako.deflate(matrix);
try {
const queryString =
!dataCollectionNameIsReadOnly && !!dataCollectionName
? `?annotation-collection-name=${encodeURIComponent(
dataCollectionName
)}`
: "";
const res = await fetch(
`${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`,
{
method: "PUT",
body: compressedMatrix,
headers: new Headers({
"Content-Type": "application/octet-stream",
}),
credentials: "include",
}
);
if (res.ok) {
dispatch({
type: "writable obs annotations - save complete",
lastSavedAnnoMatrix: annoMatrix,
});
} else {
dispatch({
type: "writable obs annotations - save error",
message: `HTTP error ${res.status} - ${res.statusText}`,
res,
});
}
} catch (error) {
dispatch({
type: "writable obs annotations - save error",
message: error.toString(),
error,
});
}
};
export const saveGenesetsAction = () => async (dispatch, getState) => {
const state = getState();
// bail if gene sets not available, or in readonly mode.
const { config } = state;
const { lastTid, genesets } = state.genesets;
const genesetsAreAvailable =
config?.parameters?.annotations_genesets ?? false;
const genesetsReadonly =
config?.parameters?.annotations_genesets_readonly ?? true;
if (!genesetsAreAvailable || genesetsReadonly) {
// our non-save was completed!
return dispatch({
type: "autosave: genesets complete",
lastSavedGenesets: genesets,
});
}
dispatch({
type: "autosave: genesets started",
});
/* Create the JSON OTA data structure */
const tid = (lastTid ?? 0) + 1;
const genesetsOTA = [];
for (const [name, gs] of genesets) {
const genes = [];
for (const g of gs.genes.values()) {
genes.push({
gene_symbol: g.geneSymbol,
gene_description: g.geneDescription,
});
}
genesetsOTA.push({
geneset_name: name,
geneset_description: gs.genesetDescription,
genes,
});
}
const ota = {
tid,
genesets: genesetsOTA,
};
/* Save to server */
try {
const { dataCollectionNameIsReadOnly, dataCollectionName } =
state.annotations;
const queryString =
!dataCollectionNameIsReadOnly && !!dataCollectionName
? `?annotation-collection-name=${encodeURIComponent(
dataCollectionName
)}`
: "";
const res = await fetch(
`${globals.API.prefix}${globals.API.version}genesets${queryString}`,
{
method: "PUT",
headers: new Headers({
Accept: "application/json",
"Content-Type": "application/json",
}),
body: JSON.stringify(ota),
credentials: "include",
}
);
if (!res.ok) {
return dispatch({
type: "autosave: genesets error",
message: `HTTP error ${res.status} - ${res.statusText}`,
res,
});
}
return Promise.all([
dispatch({
type: "autosave: genesets complete",
lastSavedGenesets: genesets,
}),
dispatch({
type: "geneset: set tid",
tid,
}),
]);
} catch (error) {
return dispatch({
type: "autosave: genesets error",
message: error.toString(),
error,
});
}
};
-530
View File
@@ -1,530 +0,0 @@
/*
Action creators for user annotation
*/
import difference from "lodash.difference";
import pako from "pako";
import * as globals from "../globals";
import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager";
const { isUserAnnotation } = AnnotationsHelpers;
export const annotationCreateCategoryAction = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
newCategoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
categoryToDuplicate: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/*
Add a new user-created category to the obs annotations.
Arguments:
newCategoryName - string name for the category.
categoryToDuplicate - obs category to use for initial values, or null.
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
const { schema } = prevAnnoMatrix;
/* name must be a string, non-zero length */
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
throw new Error("user annotations require string name");
/* ensure the name isn't already in use! */
if (schema.annotations.obsByName[newCategoryName])
throw new Error("name collision on annotation category create");
let initialValue;
let newSchema;
let ctor;
if (categoryToDuplicate) {
/* if we are duplicating a category, retrieve it */
const catDupSchema = schema.annotations.obsByName[categoryToDuplicate];
const catDupType = catDupSchema?.type;
if (catDupType !== "string" && catDupType !== "categorical")
throw new Error("categoryToDuplicate does not exist or has invalid type");
const catToDupDf = await prevAnnoMatrix
.base()
.fetch("obs", categoryToDuplicate);
const col = catToDupDf.col(categoryToDuplicate);
initialValue = col.asArray();
const { categories } = col.summarizeCategorical();
// all user-created annotations must have the unassigned category
if (!categories.includes(globals.unassignedCategoryLabel)) {
categories.push(globals.unassignedCategoryLabel);
}
ctor = initialValue.constructor;
newSchema = {
...catDupSchema,
name: newCategoryName,
categories,
writable: true,
};
} else {
/* else assign to the standard default value */
initialValue = globals.unassignedCategoryLabel;
ctor = Array;
newSchema = {
name: newCategoryName,
type: "categorical",
categories: [globals.unassignedCategoryLabel],
writable: true,
};
}
const obsCrossfilter = prevObsCrossfilter.addObsColumn(
newSchema,
ctor,
initialValue
);
dispatch({
type: "annotation: create category",
data: newCategoryName,
categoryToDuplicate,
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
});
};
export const annotationRenameCategoryAction = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
oldCategoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
newCategoryName: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => (dispatch: any, getState: any) => {
/*
Rename a user-created annotation category
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, oldCategoryName))
throw new Error("not a user annotation");
/* name must be a string, non-zero length */
if (typeof newCategoryName !== "string" || newCategoryName.length === 0)
throw new Error("user annotations require string name");
if (oldCategoryName === newCategoryName) return;
const obsCrossfilter = prevObsCrossfilter.renameObsColumn(
oldCategoryName,
newCategoryName
);
dispatch({
type: "annotation: category edited",
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
metadataField: oldCategoryName,
newCategoryText: newCategoryName,
data: newCategoryName,
});
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export const annotationDeleteCategoryAction = (categoryName: any) => (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
/*
Delete a user-created category
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
const obsCrossfilter = prevObsCrossfilter.dropObsColumn(categoryName);
dispatch({
type: "annotation: delete category",
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
metadataField: categoryName,
});
};
export const annotationCreateLabelInCategory = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
assignSelected: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/*
Add a new label to a user-defined category. If assignSelected is true, assign
the label to all currently selected cells.
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
let obsCrossfilter = prevObsCrossfilter.addObsAnnoCategory(
categoryName,
labelName
);
if (assignSelected) {
obsCrossfilter = await obsCrossfilter.setObsColumnValues(
categoryName,
prevObsCrossfilter.allSelectedLabels(),
labelName
);
}
dispatch({
type: "annotation: add new label to category",
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
metadataField: categoryName,
newLabelText: labelName,
assignSelectedCells: assignSelected,
});
};
export const annotationDeleteLabelFromCategory = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName: any // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/*
delete a label from a user-defined category
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
const obsCrossfilter = await prevObsCrossfilter.removeObsAnnoCategory(
categoryName,
labelName,
globals.unassignedCategoryLabel
);
dispatch({
type: "annotation: delete label",
metadataField: categoryName,
label: labelName,
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
});
};
export const annotationRenameLabelInCategory = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
oldLabelName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
newLabelName: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/*
label name change
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
let obsCrossfilter = await prevObsCrossfilter.resetObsColumnValues(
categoryName,
oldLabelName,
newLabelName
);
obsCrossfilter = await obsCrossfilter.removeObsAnnoCategory(
categoryName,
oldLabelName,
globals.unassignedCategoryLabel
);
dispatch({
type: "annotation: label edited",
editedLabel: newLabelName,
metadataField: categoryName,
label: oldLabelName,
annoMatrix: obsCrossfilter.annoMatrix,
obsCrossfilter,
});
};
export const annotationLabelCurrentSelection = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
labelName: any
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) => async (dispatch: any, getState: any) => {
/*
set the label on all currently selected
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
if (!prevAnnoMatrix || !prevObsCrossfilter) return;
if (!isUserAnnotation(prevAnnoMatrix, categoryName))
throw new Error("not a user annotation");
const obsCrossfilter = await prevObsCrossfilter.setObsColumnValues(
categoryName,
prevObsCrossfilter.allSelectedLabels(),
labelName
);
dispatch({
type: "annotation: label current cell selection",
metadataField: categoryName,
label: labelName,
obsCrossfilter,
annoMatrix: obsCrossfilter.annoMatrix,
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function writableAnnotations(annoMatrix: any) {
return (
annoMatrix.schema.annotations.obs.columns
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((s: any) => s.writable)
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.map((s: any) => s.name)
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export const needToSaveObsAnnotations = (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
annoMatrix: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
lastSavedAnnoMatrix: any
) => {
/*
Return true if there are LIKELY user-defined annotation modifications between the two
annoMatrices. Technically not an action creator, but intimately intertwined
with the save process.
Two conditions will trigger a need to save:
* the collection of user-defined columns have changed
* the contents of the user-defined columns have change
*/
annoMatrix = annoMatrix.base();
// if the annoMatrix hasn't changed, we are guaranteed no changes to the matrix schema or contents.
if (annoMatrix === lastSavedAnnoMatrix) return false;
// if the schema has changed, we need to save
const currentWritable = writableAnnotations(annoMatrix);
if (difference(currentWritable, writableAnnotations(lastSavedAnnoMatrix))) {
return true;
}
// no schema changes; check for change in contents
return currentWritable.some(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(col: any) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col)
);
};
// 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.
*/
const state = getState();
const { annotations, autosave } = state;
const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations;
const { lastSavedAnnoMatrix, saveInProgress } = autosave;
const annoMatrix = state.annoMatrix.base();
if (saveInProgress || annoMatrix === lastSavedAnnoMatrix) return;
if (!needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix)) {
dispatch({
type: "writable obs annotations - save complete",
lastSavedAnnoMatrix: annoMatrix,
});
return;
}
/*
Else, we really do need to save
*/
dispatch({
type: "writable obs annotations - save started",
});
const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix));
const matrix = MatrixFBS.encodeMatrixFBS(df);
const compressedMatrix = pako.deflate(matrix);
try {
const queryString =
!dataCollectionNameIsReadOnly && !!dataCollectionName
? `?annotation-collection-name=${encodeURIComponent(
dataCollectionName
)}`
: "";
const res = await fetch(
`${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`,
{
method: "PUT",
body: compressedMatrix,
headers: new Headers({
"Content-Type": "application/octet-stream",
}),
credentials: "include",
}
);
if (res.ok) {
dispatch({
type: "writable obs annotations - save complete",
lastSavedAnnoMatrix: annoMatrix,
});
} else {
dispatch({
type: "writable obs annotations - save error",
message: `HTTP error ${res.status} - ${res.statusText}`,
res,
});
}
} catch (error) {
dispatch({
type: "writable obs annotations - save error",
message: error.toString(),
error,
});
}
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export const saveGenesetsAction = () => async (
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getState: any
) => {
const state = getState();
// bail if gene sets not available, or in readonly mode.
const { config } = state;
const { lastTid, genesets } = state.genesets;
const genesetsAreAvailable =
config?.parameters?.annotations_genesets ?? false;
const genesetsReadonly =
config?.parameters?.annotations_genesets_readonly ?? true;
if (!genesetsAreAvailable || genesetsReadonly) {
// our non-save was completed!
return dispatch({
type: "autosave: genesets complete",
lastSavedGenesets: genesets,
});
}
dispatch({
type: "autosave: genesets started",
});
/* Create the JSON OTA data structure */
const tid = (lastTid ?? 0) + 1;
const genesetsOTA = [];
for (const [name, gs] of genesets) {
const genes = [];
for (const g of gs.genes.values()) {
genes.push({
gene_symbol: g.geneSymbol,
gene_description: g.geneDescription,
});
}
genesetsOTA.push({
geneset_name: name,
geneset_description: gs.genesetDescription,
genes,
});
}
const ota = {
tid,
genesets: genesetsOTA,
};
/* Save to server */
try {
const {
dataCollectionNameIsReadOnly,
dataCollectionName,
} = state.annotations;
const queryString =
!dataCollectionNameIsReadOnly && !!dataCollectionName
? `?annotation-collection-name=${encodeURIComponent(
dataCollectionName
)}`
: "";
const res = await fetch(
`${globals.API.prefix}${globals.API.version}genesets${queryString}`,
{
method: "PUT",
headers: new Headers({
Accept: "application/json",
"Content-Type": "application/json",
}),
body: JSON.stringify(ota),
credentials: "include",
}
);
if (!res.ok) {
return dispatch({
type: "autosave: genesets error",
message: `HTTP error ${res.status} - ${res.statusText}`,
res,
});
}
return await Promise.all([
dispatch({
type: "autosave: genesets complete",
lastSavedGenesets: genesets,
}),
dispatch({
type: "geneset: set tid",
tid,
}),
]);
} catch (error) {
return dispatch({
type: "autosave: genesets error",
message: error.toString(),
error,
});
}
};
+47
View File
@@ -0,0 +1,47 @@
/*
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,
});
};
-60
View File
@@ -1,60 +0,0 @@
/*
action creators related to embeddings choice
*/
import { Action, ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
import { AnnoMatrixObsCrossfilter } from "../annoMatrix";
import type { AppDispatch, RootState } from "../reducers";
import { _setEmbeddingSubset } from "../util/stateManager/viewStackHelpers";
import { Field } from "../common/types/schema";
// 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(Field.emb, newEmbeddingName, {
mode: "all",
});
return [annoMatrix, obsCrossfilter];
}
export const layoutChoiceAction: ActionCreator<
ThunkAction<Promise<void>, RootState, never, Action<"set layout choice">>
> =
(newLayoutChoice: string) =>
async (dispatch: AppDispatch, getState: () => RootState): Promise<void> => {
/*
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,45 +21,34 @@ 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.
*/ */
import { Dataframe } from "../util/dataframe"; export const genesetDelete = (genesetName) => (dispatch, getState) => {
const state = getState();
export const genesetDelete = const { genesets } = state;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. const gs = genesets?.genesets?.get(genesetName) ?? {};
(genesetName: any) => (dispatch: any, getState: any) => { const geneSymbols = Array.from(gs.genes.keys());
const state = getState(); const obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols);
const { genesets } = state; if (genesetName === state.colors.colorAccessor) {
const gs = genesets?.genesets?.get(genesetName) ?? {};
const geneSymbols = Array.from(gs.genes.keys());
const obsCrossfilter = dropGeneset(
dispatch,
state,
genesetName,
geneSymbols
);
if (genesetName === state.colors.colorAccessor) {
dispatch({
type: "reset colorscale",
});
}
dispatch({ dispatch({
type: "geneset: delete", type: "reset colorscale",
genesetName,
obsCrossfilter,
annoMatrix: obsCrossfilter.annoMatrix,
}); });
}; }
dispatch({
type: "geneset: delete",
genesetName,
obsCrossfilter,
annoMatrix: obsCrossfilter.annoMatrix,
});
};
export const genesetAddGenes = export const genesetAddGenes =
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. (genesetName, genes) => async (dispatch, getState) => {
(genesetName: any, genes: any) => async (dispatch: any, getState: any) => {
const state = getState(); const state = getState();
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state; const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
const { schema } = annoMatrix; const { schema } = annoMatrix;
const varIndex = schema.annotations.var.index; const varIndex = schema.annotations.var.index;
const df: Dataframe = await annoMatrix.fetch("var", varIndex); const df = await annoMatrix.fetch("var", varIndex);
const geneNames = df.col(varIndex).asArray(); const geneNames = df.col(varIndex).asArray();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. genes = genes.reduce((acc, gene) => {
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.`
@@ -88,8 +77,7 @@ export const genesetAddGenes =
}; };
export const genesetDeleteGenes = export const genesetDeleteGenes =
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. (genesetName, geneSymbols) => (dispatch, getState) => {
(genesetName: any, geneSymbols: any) => (dispatch: any, getState: any) => {
const state = getState(); const state = getState();
const obsCrossfilter = dropGeneset( const obsCrossfilter = dropGeneset(
dispatch, dispatch,
@@ -110,14 +98,7 @@ export const genesetDeleteGenes =
Private Private
*/ */
function dropGenesetSummaryDimension( function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) {
// 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) ?? {};
@@ -133,8 +114,7 @@ function dropGenesetSummaryDimension(
return obsCrossfilter.dropDimension("X", query); return obsCrossfilter.dropDimension("X", query);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function dropGeneDimension(obsCrossfilter, state, gene) {
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 = {
@@ -147,21 +127,10 @@ function dropGeneDimension(obsCrossfilter: any, state: any, gene: any) {
return obsCrossfilter.dropDimension("X", query); return obsCrossfilter.dropDimension("X", query);
} }
function dropGeneset( function dropGeneset(dispatch, state, genesetName, geneSymbols) {
// 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(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (crossfilter, gene) => dropGeneDimension(crossfilter, state, gene),
(crossfilter: any, gene: any) =>
dropGeneDimension(crossfilter, state, gene),
dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName) dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName)
); );
dispatch({ dispatch({
@@ -169,8 +138,7 @@ function dropGeneset(
continuousNamespace: { isGeneSetSummary: true }, continuousNamespace: { isGeneSetSummary: true },
selection: genesetName, selection: genesetName,
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. geneSymbols.forEach((g) =>
geneSymbols.forEach((g: any) =>
dispatch({ dispatch({
type: "continuous metadata histogram cancel", type: "continuous metadata histogram cancel",
continuousNamespace: { isUserDefined: true }, continuousNamespace: { isUserDefined: true },
@@ -1,4 +1,3 @@
import type { Config } from "../globals";
import * as globals from "../globals"; import * as globals from "../globals";
import { AnnoMatrixLoader, AnnoMatrixObsCrossfilter } from "../annoMatrix"; import { AnnoMatrixLoader, AnnoMatrixObsCrossfilter } from "../annoMatrix";
import { import {
@@ -12,12 +11,8 @@ import * as annoActions from "./annotation";
import * as viewActions from "./viewStack"; 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";
import { AppDispatch, RootState } from "../reducers";
import { EmbeddingSchema, Schema } from "../common/types/schema";
import { UserInfoPayload } from "../reducers/userInfo";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function setGlobalConfig(config) {
function setGlobalConfig(config: any) {
/** /**
* Set any global run-time config not _exclusively_ managed by the config reducer. * Set any global run-time config not _exclusively_ managed by the config reducer.
* This should only set fields defined in globals.globalConfig. * This should only set fields defined in globals.globalConfig.
@@ -30,8 +25,7 @@ function setGlobalConfig(config: any) {
/* /*
return promise fetching user-configured colors return promise fetching user-configured colors
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function userColorsFetchAndLoad(dispatch) {
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",
@@ -40,38 +34,36 @@ async function userColorsFetchAndLoad(dispatch: any) {
); );
} }
async function schemaFetch(): Promise<{ schema: Schema }> { async function schemaFetch() {
return fetchJson<{ schema: Schema }>("schema"); return fetchJson("schema");
} }
async function configFetch(dispatch: AppDispatch): Promise<Config> { async function configFetch(dispatch) {
const response = await fetchJson<{ config: globals.Config }>("config"); return fetchJson("config").then((response) => {
const config = { ...globals.configDefaults, ...response.config }; const config = { ...globals.configDefaults, ...response.config };
setGlobalConfig(config); setGlobalConfig(config);
dispatch({ dispatch({
type: "configuration load complete", type: "configuration load complete",
config, config,
});
return config;
}); });
return config;
} }
async function userInfoFetch(dispatch: AppDispatch): Promise<UserInfoPayload> { async function userInfoFetch(dispatch) {
return fetchJson<{ userinfo: UserInfoPayload }>("userinfo").then( return fetchJson("userinfo").then((response) => {
(response) => { const { userinfo: userInfo } = response || {};
const { userinfo: userInfo } = response || {}; dispatch({
dispatch({ type: "userInfo load complete",
type: "userInfo load complete", userInfo,
userInfo, });
}); return userInfo;
return userInfo; });
}
);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function genesetsFetch(dispatch, config) {
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: [],
@@ -92,32 +84,26 @@ async function genesetsFetch(dispatch: any, config: any) {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. function prefetchEmbeddings(annoMatrix) {
function prefetchEmbeddings(annoMatrix: any) {
/* /*
prefetch requests for all embeddings prefetch requests for all embeddings
*/ */
const { schema } = annoMatrix; const { schema } = annoMatrix;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. const available = schema.layout.obs.map((v) => v.name);
const available = schema.layout.obs.map((v: any) => v.name); available.forEach((embName) => annoMatrix.prefetch("emb", embName));
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
available.forEach((embName: any) => annoMatrix.prefetch("emb", embName));
} }
/* /*
Application bootstrap Application bootstrap
*/ */
const doInitialDataLoad = (): (( const doInitialDataLoad = () =>
dispatch: AppDispatch, catchErrorsWrap(async (dispatch) => {
getState: () => RootState
) => void) =>
catchErrorsWrap(async (dispatch: AppDispatch) => {
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),
schemaFetch(), schemaFetch(dispatch),
userColorsFetchAndLoad(dispatch), userColorsFetchAndLoad(dispatch),
userInfoFetch(dispatch), userInfoFetch(dispatch),
]); ]);
@@ -140,7 +126,7 @@ const doInitialDataLoad = (): ((
const layoutSchema = schema?.schema?.layout?.obs ?? []; const layoutSchema = schema?.schema?.layout?.obs ?? [];
if ( if (
defaultEmbedding && defaultEmbedding &&
layoutSchema.some((s: EmbeddingSchema) => s.name === defaultEmbedding) layoutSchema.some((s) => s.name === defaultEmbedding)
) { ) {
dispatch(embActions.layoutChoiceAction(defaultEmbedding)); dispatch(embActions.layoutChoiceAction(defaultEmbedding));
} }
@@ -149,25 +135,21 @@ const doInitialDataLoad = (): ((
} }
}, true); }, true);
// 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) {
function requestSingleGeneExpressionCountsForColoringPOST(gene: any) {
return { return {
type: "color by expression", type: "color by expression",
gene, 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) => ({
const requestUserDefinedGene = (gene: any) => ({
type: "request user defined gene success", type: "request user defined gene success",
data: { data: {
genes: [gene], genes: [gene],
}, },
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. const dispatchDiffExpErrors = (dispatch, response) => {
const dispatchDiffExpErrors = (dispatch: any, response: any) => {
switch (response.status) { switch (response.status) {
case 403: case 403:
dispatchNetworkErrorMessageToUser( dispatchNetworkErrorMessageToUser(
@@ -191,16 +173,8 @@ const dispatchDiffExpErrors = (dispatch: any, response: any) => {
}; };
const requestDifferentialExpression = const requestDifferentialExpression =
( (set1, set2, 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, 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.
) =>
// 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 {
/* /*
@@ -248,9 +222,7 @@ const requestDifferentialExpression =
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)) {
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message diffexpLists[polarity] = response[polarity].map((v) => [
// 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),
]); ]);
@@ -269,10 +241,10 @@ const requestDifferentialExpression =
} }
}; };
function fetchJson<T>(pathAndQuery: string): Promise<T> { function fetchJson(pathAndQuery) {
return doJsonRequest<T>( return doJsonRequest(
`${globals.API.prefix}${globals.API.version}${pathAndQuery}` `${globals.API.prefix}${globals.API.version}${pathAndQuery}`
) as Promise<T>; );
} }
export default { export default {
+192
View File
@@ -0,0 +1,192 @@
/*
Action creators for selection
*/
export const selectContinuousMetadataAction =
(type, query, range, oldProps = {}) =>
async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = range
? {
mode: "range",
lo: range[0],
hi: range[1],
inclusive: true, // [lo, hi] incluisve selection
}
: { mode: "all" };
const obsCrossfilter = await prevObsCrossfilter.select(...query, selection);
dispatch({
type,
obsCrossfilter,
range,
...oldProps,
});
};
export const selectCategoricalMetadataAction =
(
type, // action type
metadataField, // annotation category name
labels,
label, // the label being selected/deselected
isSelected, // bool
oldProps = {}
) =>
async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter, categoricalSelection } =
getState();
const labelSelectionState = new Map(categoricalSelection[metadataField]);
labels.forEach(
(l) => labelSelectionState.has(l) || labelSelectionState.set(l, true)
);
labelSelectionState.set(label, isSelected);
const values = Array.from(labelSelectionState.keys()).filter((k) =>
labelSelectionState.get(k)
);
const selection = {
mode: "exact",
values,
};
const obsCrossfilter = await prevObsCrossfilter.select(
"obs",
metadataField,
selection
);
dispatch({
type,
obsCrossfilter,
metadataField,
labelSelectionState,
...oldProps,
});
};
export const selectCategoricalAllMetadataAction =
(
type, // action type
metadataField, // annotation category name
labels,
isSelected, // bool, select all or none
oldProps = {}
) =>
async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter, categoricalSelection } =
getState();
const labelSelectionState = new Map(categoricalSelection[metadataField]);
labels.forEach((label) => labelSelectionState.set(label, isSelected));
const selection = { mode: isSelected ? "all" : "none" };
const obsCrossfilter = await prevObsCrossfilter.select(
"obs",
metadataField,
selection
);
dispatch({
type,
obsCrossfilter,
metadataField,
labelSelectionState,
...oldProps,
});
};
/**
** Graph selection-related actions
**/
export const graphBrushStartAction = () =>
/* no change to crossfilter until a change fires */
({ type: "graph brush start" });
const _graphBrushWithinRectAction =
(type, embName, brushCoords) => async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = { mode: "within-rect", ...brushCoords };
const obsCrossfilter = await prevObsCrossfilter.select(
"emb",
embName,
selection
);
dispatch({
type,
obsCrossfilter,
brushCoords,
});
};
const _graphAllAction = (type, embName) => async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const obsCrossfilter = await prevObsCrossfilter.select("emb", embName, {
mode: "all",
});
dispatch({
type,
obsCrossfilter,
});
};
export const graphBrushChangeAction = (embName, brushCoords) =>
_graphBrushWithinRectAction("graph brush change", embName, brushCoords);
export const graphBrushEndAction = (embName, brushCoords) =>
_graphBrushWithinRectAction("graph brush end", embName, brushCoords);
export const graphBrushCancelAction = (embName) =>
_graphAllAction("graph brush cancel", embName);
export const graphBrushDeselectAction = (embName) =>
_graphAllAction("graph brush deselect", embName);
export const graphLassoStartAction = () =>
/* no change to crossfilter until a change fires */
({ type: "graph lasso start" });
export const graphLassoCancelAction = (embName) =>
_graphAllAction("graph lasso cancel", embName);
export const graphLassoDeselectAction = (embName) =>
_graphAllAction("graph lasso cancel", embName);
export const graphLassoEndAction =
(embName, polygon) => async (dispatch, getState) => {
const { obsCrossfilter: prevObsCrossfilter } = getState();
const selection = {
mode: "within-polygon",
polygon,
};
const obsCrossfilter = await prevObsCrossfilter.select(
"emb",
embName,
selection
);
dispatch({
type: "graph lasso end",
obsCrossfilter,
polygon,
});
};
/*
Differential expression set selection
*/
export const setCellSetFromSelection = (cellSetId) => (dispatch, getState) => {
const { obsCrossfilter } = getState();
const selected = obsCrossfilter.allSelectedLabels();
dispatch({
type: `store current cell selection as differential set ${cellSetId}`,
data: selected.length > 0 ? selected : null,
});
};
-248
View File
@@ -1,248 +0,0 @@
/*
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,13 +18,7 @@ import {
_userResetSubsetAnnoMatrix, _userResetSubsetAnnoMatrix,
} from "../util/stateManager/viewStackHelpers"; } from "../util/stateManager/viewStackHelpers";
// 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, max) => (dispatch, getState) => {
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.
@@ -40,8 +34,7 @@ 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. export const subsetAction = () => (dispatch, getState) => {
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.
@@ -49,10 +42,8 @@ export const subsetAction = () => (dispatch: any, getState: any) => {
By convention, a clip view is ALWAYS the top view, so if present, pop By convention, a clip view is ALWAYS the top view, so if present, pop
off and re-apply off and re-apply
*/ */
const { const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
annoMatrix: prevAnnoMatrix, getState();
obsCrossfilter: prevObsCrossfilter,
} = getState();
const annoMatrix = _userSubsetAnnoMatrix( const annoMatrix = _userSubsetAnnoMatrix(
prevAnnoMatrix, prevAnnoMatrix,
prevObsCrossfilter.allSelectedMask() prevObsCrossfilter.allSelectedMask()
@@ -65,8 +56,7 @@ export const subsetAction = () => (dispatch: any, getState: any) => {
}); });
}; };
// 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, getState) => {
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
@@ -2,9 +2,6 @@ import {
Dataframe, Dataframe,
IdentityInt32Index, IdentityInt32Index,
dataframeMemo, dataframeMemo,
LabelType,
DataframeValue,
DataframeValueArray,
} from "../util/dataframe"; } from "../util/dataframe";
import { import {
_getColumnDimensionNames, _getColumnDimensionNames,
@@ -13,71 +10,13 @@ import {
_getWritableColumns, _getWritableColumns,
} from "./schema"; } from "./schema";
import { indexEntireSchema } from "../util/stateManager/schemaHelpers"; import { indexEntireSchema } from "../util/stateManager/schemaHelpers";
import { import { _whereCacheGet, _whereCacheMerge } from "./whereCache";
_whereCacheGet,
_whereCacheMerge,
WhereCache,
WhereCacheColumnLabels,
} from "./whereCache";
import _shallowClone from "./clone"; import _shallowClone from "./clone";
import { _queryValidate, _queryCacheKey, Query } from "./query"; import { _queryValidate, _queryCacheKey } from "./query";
import { GCHints } from "../common/types/entities";
import {
AnnotationColumnSchema,
Category,
Field,
EmbeddingSchema,
Schema,
ArraySchema,
RawSchema,
} from "../common/types/schema";
import { LabelArray } from "../util/dataframe/types";
import { LabelIndexBase } from "../util/dataframe/labelIndex";
const _dataframeCache = dataframeMemo(128); const _dataframeCache = dataframeMemo(128);
interface Cache { export default class AnnoMatrix {
[Field.obs]: Dataframe;
[Field.var]: Dataframe;
[Field.emb]: Dataframe;
[Field.X]: Dataframe;
}
interface PendingLoad {
[Field.obs]: { [key: string]: Promise<void> };
[Field.var]: { [key: string]: Promise<void> };
[Field.emb]: { [key: string]: Promise<void> };
[Field.X]: { [key: string]: Promise<void> };
}
export interface UserFlags {
isUserSubsetView?: boolean;
isEmbSubsetView?: boolean;
}
export default abstract class AnnoMatrix {
public isView: boolean;
public nObs: number;
public nVar: number;
public rowIndex: LabelIndexBase;
public schema: Schema;
public userFlags: UserFlags;
public viewOf: AnnoMatrix;
public _cache: Cache;
private _pendingLoad: PendingLoad;
private _whereCache: WhereCache;
private _gcInfo: Map<string, number>;
/* /*
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.
@@ -108,19 +47,14 @@ export default abstract class AnnoMatrix {
subset(annoMatrix, rowLabels) -> annoMatrix subset(annoMatrix, rowLabels) -> annoMatrix
etc. etc.
*/ */
static fields(): Field[] { static fields() {
/* /*
return the fields present in the AnnoMatrix instance. return the fields present in the AnnoMatrix instance.
*/ */
return [Field.obs, Field.var, Field.emb, Field.X]; return ["obs", "var", "emb", "X"];
} }
constructor( constructor(schema, nObs, nVar, rowIndex = null) {
schema: RawSchema,
nObs: number,
nVar: number,
rowIndex: LabelIndexBase | null = null
) {
/* /*
Private constructor - this is an abstract base class. Do not use. Private constructor - this is an abstract base class. Do not use.
*/ */
@@ -136,7 +70,7 @@ export default abstract class AnnoMatrix {
* rowIndex - a rowIndex shared by all data on this view (ie, the list of cells). * rowIndex - a rowIndex shared by all data on this view (ie, the list of cells).
The row index labels are as defined by the base dataset from the server. The row index labels are as defined by the base dataset from the server.
* isView - true if this is a view, false if not. * isView - true if this is a view, false if not.
* viewOf - pointer to parent annomatrix if a view, self if not a view. * viewOf - pointer to parent annomatrix if a view, undefined/null if not a view.
* userFlags - container for any additional state a user of this API wants to hang * userFlags - container for any additional state a user of this API wants to hang
off of an annoMatrix, and have propagated by the (shallow) cloning protocol. off of an annoMatrix, and have propagated by the (shallow) cloning protocol.
*/ */
@@ -145,17 +79,17 @@ export default abstract class AnnoMatrix {
this.nVar = nVar; this.nVar = nVar;
this.rowIndex = rowIndex || new IdentityInt32Index(nObs); this.rowIndex = rowIndex || new IdentityInt32Index(nObs);
this.isView = false; this.isView = false;
this.viewOf = this; this.viewOf = undefined;
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),
@@ -168,14 +102,14 @@ export default abstract class AnnoMatrix {
emb: {}, emb: {},
X: {}, X: {},
}; };
this._whereCache = {} as WhereCache; this._whereCache = {};
this._gcInfo = new Map(); this._gcInfo = new Map();
} }
/** /**
** Schema helper/accessors ** Schema helper/accessors
**/ **/
getMatrixColumns(field: Field): string[] { 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
obs, var and emb fields. X currently unimplemented and will throw. obs, var and emb fields. X currently unimplemented and will throw.
@@ -188,7 +122,7 @@ export default abstract class AnnoMatrix {
} }
// 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 -- need to be able to call this on instances
getMatrixFields(): Field[] { getMatrixFields() {
/* /*
Return array of fields in this annoMatrix. Currently hard-wired to Return array of fields in this annoMatrix. Currently hard-wired to
return: ["X", "obs", "var", "emb"]. return: ["X", "obs", "var", "emb"].
@@ -198,7 +132,7 @@ export default abstract class AnnoMatrix {
return AnnoMatrix.fields(); return AnnoMatrix.fields();
} }
getColumnSchema(field: Field, col: LabelType): ArraySchema { getColumnSchema(field, col) {
/* /*
Return the schema for the field & column ,eg, Return the schema for the field & column ,eg,
@@ -210,7 +144,7 @@ export default abstract class AnnoMatrix {
return _getColumnSchema(this.schema, field, col); return _getColumnSchema(this.schema, field, col);
} }
getColumnDimensions(field: Field, col: LabelType): LabelArray | undefined { 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,
this just return the column name. Multi-dimensional columns, such as embeddings, this just return the column name. Multi-dimensional columns, such as embeddings,
@@ -228,19 +162,19 @@ export default abstract class AnnoMatrix {
/** /**
** General utility methods ** General utility methods
**/ **/
base(): AnnoMatrix { base() {
/* /*
return the base of view, or `this` if not a view. return the base of view, or `this` if not a view.
*/ */
let annoMatrix = this._getViewOf(); let annoMatrix = this;
while (annoMatrix.isView) annoMatrix = annoMatrix._getViewOf(); while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf;
return annoMatrix; return annoMatrix;
} }
/** /**
** Load / read interfaces ** Load / read interfaces
**/ **/
fetch(field: Field, q: Query | Query[]): Promise<Dataframe> { 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.
Currently supports ONLY full column query. Currently supports ONLY full column query.
@@ -269,7 +203,7 @@ export default abstract class AnnoMatrix {
1. Fetch the "n_genes" column the "obs": 1. Fetch the "n_genes" column the "obs":
const df = await fetch("obs", "n_genes") const df = await fetch("obs", "n_genes")
console.log("Largest number of genes is: ", df.summarizeContinuous().max); console.log("Largest number of genes is: ", df.summarize().max);
2. Fetch two separate columns from obs. Returns a single dataframe containing 2. Fetch two separate columns from obs. Returns a single dataframe containing
the columns: the columns:
@@ -297,7 +231,7 @@ export default abstract class AnnoMatrix {
return this._fetch(field, q); return this._fetch(field, q);
} }
prefetch(field: Field, q: Query): void { 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
not return a value. not return a value.
@@ -306,6 +240,7 @@ export default abstract class AnnoMatrix {
overall component rendering latency. overall component rendering latency.
*/ */
this._fetch(field, q); this._fetch(field, q);
return undefined;
} }
/** /**
@@ -326,172 +261,176 @@ export default abstract 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
Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix. addObsAnnoCategory(col, category) {
Typical use is to add a new user-created label to a user-created obs categorical /*
annotation. Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix.
Typical use is to add a new user-created label to a user-created obs categorical
annotation.
Will throw column does not exist or is not writable. Will throw column does not exist or is not writable.
Example: Example:
addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix
*/
abstract addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix;
/*
Remove a category value from an obs column, reassign any obs having that value
to the 'unassignedCategory' value, and return a promise for a new AnnoMatrix.
Typical use is to remove a user-created label from a user-created obs categorical
annotation.
Will throw column does not exist or is not writable.
An `unassignedCategory` value must be provided, for assignment to any obs/cells
that had the now-delete category label as their value.
Example:
await removeObsAnnoCategory("my-tissue-type", "right earlobe", "unassigned") -> AnnoMatrix
NOTE: method is async as it may need to fetch data to provide the reassignment.
*/
abstract removeObsAnnoCategory(
col: LabelType,
category: Category,
unassignedCategory: string
): Promise<AnnoMatrix>;
/*
Drop an entire writable column, eg a user-created obs annotation. Typical use
is to provide the "Delete Category" implementation. Returns the new AnnoMatrix.
Will throw if not a writable annotation.
Will throw column does not exist or is not writable.
Example:
dropObsColumn("old annotations") -> AnnoMatrix
*/
abstract dropObsColumn(col: LabelType): AnnoMatrix;
/*
Add a new writable OBS annotation column, with the caller-specified schema, initial value
type and value.
Value may be any one of:
* an array of values
* a primitive type, including null or undefined.
If an array, length must be the same as 'this.nObs', and constructor must equal 'Ctor'.
If a primitive, 'Ctor' will be used to create the initial value, which will be filled
with 'value'.
Throws if the name specified in 'colSchema' duplicates an existing obs column.
Returns a new AnnoMatrix.
Examples:
addObsColumn(
{ name: "foo", type: "categorical", categories: "unassigned" },
Array,
"unassigned"
) -> AnnoMatrix
*/
abstract addObsColumn<T extends DataframeValueArray>(
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): AnnoMatrix;
/*
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
Will throw column does not exist or is not writable, or if 'newCol' is not unique.
Example:
renameObsColumn('cell type', 'old cell type') -> AnnoMatrix.
*/
abstract renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix;
/*
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
to set a group of cells to have a label on a user-created categorical annotation
(eg set all selected cells to have a label).
NOTE: async method, as it may need to fetch.
Will throw column does not exist or is not writable.
Example:
await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix
*/
abstract setObsColumnValues(
col: LabelType,
obsLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrix>;
/*
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
Async method - returns a promise for a new AnnoMatrix.
Typical use would be to set all labels of one value to another.
Will throw column does not exist or is not writable.
Example:
await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix
*/ */
abstract resetObsColumnValues<T extends DataframeValue>( _subclassResponsibility();
col: LabelType, }
oldValue: T,
newValue: T
): Promise<AnnoMatrix>;
/* // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
Add a new obs embedding to the AnnoMatrix, with provided schema. async removeObsAnnoCategory(col, category, unassignedCategory) {
Returns a new annomatrix.
Typical use will be to add a re-embedding that the server has calculated.
Will throw if the column schema is invalid (eg, duplicate name).
*/
abstract addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix;
getCacheKeys(
field: Field,
query: Query
): WhereCacheColumnLabels | [undefined] {
/* /*
Return cache keys for columns associated with this query. May return Remove a category value from an obs column, reassign any obs having that value
[unknown] if no keys are known (ie, nothing is or was cached). to the 'unassignedCategory' value, and return a promise for a new AnnoMatrix.
*/ Typical use is to remove a user-created label from a user-created obs categorical
annotation.
Will throw column does not exist or is not writable.
An `unassignedCategory` value must be provided, for assignment to any obs/cells
that had the now-delete category label as their value.
Example:
await removeObsAnnoCategory("my-tissue-type", "right earlobe", "unassigned") -> AnnoMatrix
NOTE: method is async as it may need to fetch data to provide the reassignment.
*/
_subclassResponsibility();
}
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
dropObsColumn(col) {
/*
Drop an entire writable column, eg a user-created obs annotation. Typical use
is to provide the "Delete Category" implementation. Returns the new AnnoMatrix.
Will throw if not a writable annotation.
Will throw column does not exist or is not writable.
Example:
dropObsColumn("old annotations") -> AnnoMatrix
*/
_subclassResponsibility();
}
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
addObsColumn(colSchema, Ctor, value) {
/*
Add a new writable OBS annotation column, with the caller-specified schema, initial value
type and value.
Value may be any one of:
* an array of values
* a primitive type, including null or undefined.
If an array, length must be the same as 'this.nObs', and constructor must equal 'Ctor'.
If a primitive, 'Ctor' will be used to create the initial value, which will be filled
with 'value'.
Throws if the name specified in 'colSchema' duplicates an existing obs column.
Returns a new AnnoMatrix.
Examples:
addObsColumn(
{ name: "foo", type: "categorical", categories: "unassigned" },
Array,
"unassigned"
) -> AnnoMatrix
*/
_subclassResponsibility();
}
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
renameObsColumn(oldCol, newCol) {
/*
Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix.
Will throw column does not exist or is not writable, or if 'newCol' is not unique.
Example:
renameObsColumn('cell type', 'old cell type') -> AnnoMatrix.
*/
_subclassResponsibility();
}
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
async setObsColumnValues(col, obsLabels, value) {
/*
Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be
to set a group of cells to have a label on a user-created categorical anntoation
(eg set all selected cells to have a label).
NOTE: async method, as it may need to fetch.
Will throw column does not exist or is not writable.
Example:
await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix
*/
_subclassResponsibility();
}
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
async resetObsColumnValues(col, oldValue, newValue) {
/*
Set by value - all elements in the column with value 'oldValue' are set to 'newValue'.
Async method - returns a promise for a new AnnoMatrix.
Typical use would be to set all labels of one value to another.
Will throw column does not exist or is not writable.
Example:
await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix
*/
_subclassResponsibility();
}
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
addEmbedding(colSchema) {
/*
Add a new obs embedding to the AnnoMatrix, with provided schema.
Returns a new annomatrix.
Typical use will be to add a re-embedding that the server has calculated.
Will throw if the column schema is invalid (eg, duplicate name).
*/
_subclassResponsibility();
}
getCacheKeys(field, query) {
/*
Return cache keys for columns associated with this query. May return
[unknown] if no keys are known (ie, nothing is or was cached).
*/
return _whereCacheGet(this._whereCache, this.schema, field, query); return _whereCacheGet(this._whereCache, this.schema, field, query);
} }
/** /**
** Private interfaces below. ** Private interfaces below.
**/ **/
_resolveCachedQueries(field: Field, queries: Query[]): LabelArray { _resolveCachedQueries(field, queries) {
return queries return queries
.map((query: Query) => .map((query) =>
// @ts-expect-error --- TODO revisit:
// `filter`: This expression is not callable.
_whereCacheGet(this._whereCache, this.schema, field, query).filter( _whereCacheGet(this._whereCache, this.schema, field, query).filter(
(cacheKey?: LabelType) => (cacheKey) =>
cacheKey !== undefined && this._cache[field].hasCol(cacheKey) cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
) )
) )
.flat(); .flat();
} }
async _fetch(field: Field, q: Query | Query[]): Promise<Dataframe> { async _fetch(field, q) {
if (!AnnoMatrix.fields().includes(field)) return Dataframe.empty(); if (!AnnoMatrix.fields().includes(field)) return undefined;
const queries = Array.isArray(q) ? q : [q]; const queries = Array.isArray(q) ? q : [q];
queries.forEach(_queryValidate); queries.forEach(_queryValidate);
@@ -502,7 +441,7 @@ Return cache keys for columns associated with this query. May return
/* 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(
(cacheKey?: LabelType) => (cacheKey) =>
cacheKey === undefined || !this._cache[field].hasCol(cacheKey) cacheKey === undefined || !this._cache[field].hasCol(cacheKey)
) )
); );
@@ -511,19 +450,15 @@ Return cache keys for columns associated with this query. May return
if (uncachedQueries.length > 0) { if (uncachedQueries.length > 0) {
await Promise.all( await Promise.all(
uncachedQueries.map((query) => uncachedQueries.map((query) =>
this._getPendingLoad( this._getPendingLoad(field, query, async (_field, _query) => {
field, /* fetch, then index. _doLoad is subclass interface */
query, const [whereCacheUpdate, df] = await this._doLoad(_field, _query);
async (_field: Field, _query: Query): Promise<void> => { this._cache[_field] = this._cache[_field].withColsFrom(df);
/* fetch, then index. _doLoad is subclass interface */ this._whereCache = _whereCacheMerge(
const [whereCacheUpdate, df] = await this._doLoad(_field, _query); this._whereCache,
this._cache[_field] = this._cache[_field].withColsFrom(df); whereCacheUpdate
this._whereCache = _whereCacheMerge( );
this._whereCache, })
whereCacheUpdate
);
}
)
) )
); );
} }
@@ -537,11 +472,7 @@ Return cache keys for columns associated with this query. May return
return response; return response;
} }
async _getPendingLoad( async _getPendingLoad(field, query, fetchFn) {
field: Field,
query: Query,
fetchFn: (_field: Field, _query: Query) => Promise<void>
): Promise<void> {
/* /*
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
fetch at any given time. If multiple requests occur while a fetch is fetch at any given time. If multiple requests occur while a fetch is
@@ -562,22 +493,9 @@ Return cache keys for columns associated with this query. May return
return this._pendingLoad[field][key]; return this._pendingLoad[field][key];
} }
abstract _doLoad( // eslint-disable-next-line class-methods-use-this -- make sure subclass implements
field: Field, async _doLoad() {
query: Query _subclassResponsibility();
): Promise<[WhereCache | null, Dataframe]>;
/**
* Determines viewOf for this annoMatrix.
*
* @internal
* @returns - parent annoMatrix if this annoMatrix is a view, otherwise this annoMatrix if it's not a view.
*/
_getViewOf(): AnnoMatrix {
if (this.isView) {
return this.viewOf;
}
return this;
} }
/** /**
@@ -609,21 +527,20 @@ Return cache keys for columns associated with this query. May return
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.
*/ */
_gcField(field: Field, isHot: boolean, pinnedColumns: LabelArray): void { _gcField(field, isHot, pinnedColumns) {
const maxColumns = isHot ? 256 : 10; const maxColumns = isHot ? 256 : 10; // maybe to aggressive?
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 --- TODO revisit: .filter((col) => !pinnedColumns.includes(col));
// `col`: Argument of type 'LabelType' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'.
.filter((col: LabelType) => !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;
candidates.sort((a: LabelType, b: LabelType) => { 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;
@@ -640,49 +557,41 @@ Return cache keys for columns associated with this query. May return
// ", " // ", "
// )}]` // )}]`
// ); // );
// @ts-expect-error --- TODO revisit:
// `reduce`: This expression is not callable.
this._cache[field] = toDrop.reduce( this._cache[field] = toDrop.reduce(
(df: Dataframe, col: LabelType) => df.dropCol(col), (df, col) => df.dropCol(col),
this._cache[field] this._cache[field]
); );
toDrop.forEach((col: LabelType) => toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col)));
_gcInfo.delete(_columnCacheKey(field, col))
);
} }
} }
_gcFetchCleanup(field: Field, pinnedColumns: LabelArray): void { _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
only want to gc X. only want to gc X.
*/ */
if (field === Field.X) { if (field === "X") {
this._gcField( this._gcField(
field, field,
true, true,
// @ts-expect-error --- TODO revisit:
// Property 'concat' does not exist on type 'LabelArray'.
pinnedColumns.concat(_getWritableColumns(this.schema, field)) pinnedColumns.concat(_getWritableColumns(this.schema, field))
); );
} }
} }
_gc(hints: GCHints): void { _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,
or false if we are in some other context (eg, history state). or false if we are in some other context (eg, history state).
*/ */
const { isHot } = hints; const { isHot } = hints;
const candidateFields = isHot const candidateFields = isHot ? ["X"] : ["X", "emb", "var", "obs"];
? [Field.X]
: [Field.X, Field.emb, Field.var, Field.obs];
candidateFields.forEach((field) => candidateFields.forEach((field) =>
this._gcField(field, isHot, _getWritableColumns(this.schema, field)) this._gcField(field, isHot, _getWritableColumns(this.schema, field))
); );
} }
_gcUpdateStats(field: Field, dataframe: Dataframe): void { _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
information. Currently, this is just a simple last-fetched timestamp, stored information. Currently, this is just a simple last-fetched timestamp, stored
@@ -691,7 +600,7 @@ Return cache keys for columns associated with this query. May return
const cols = dataframe.colIndex.labels(); const cols = dataframe.colIndex.labels();
const { _gcInfo } = this; const { _gcInfo } = this;
const now = Date.now(); const now = Date.now();
cols.forEach((c: LabelType) => { cols.forEach((c) => {
_gcInfo.set(_columnCacheKey(field, c), now); _gcInfo.set(_columnCacheKey(field, c), now);
}); });
} }
@@ -708,7 +617,7 @@ Return cache keys for columns associated with this query. May return
Do not override _clone(); Do not override _clone();
**/ **/
_cloneDeeper(clone: AnnoMatrix): AnnoMatrix { _cloneDeeper(clone) {
clone._cache = _shallowClone(this._cache); clone._cache = _shallowClone(this._cache);
clone._gcInfo = new Map(); clone._gcInfo = new Map();
clone._pendingLoad = { clone._pendingLoad = {
@@ -720,7 +629,7 @@ Return cache keys for columns associated with this query. May return
return clone; return clone;
} }
_clone(): AnnoMatrix { _clone() {
const clone = _shallowClone(this); const clone = _shallowClone(this);
this._cloneDeeper(clone); this._cloneDeeper(clone);
Object.seal(clone); Object.seal(clone);
@@ -731,6 +640,11 @@ Return cache keys for columns associated with this query. May return
/* /*
private utility functions below private utility functions below
*/ */
function _columnCacheKey(field: Field, column: LabelType): string { function _columnCacheKey(field, column) {
return `${field}/${column}`; return `${field}/${column}`;
} }
function _subclassResponsibility() {
/* protect against bugs in subclass */
throw new Error("subclass failed to implement required method");
}
@@ -1,7 +1,6 @@
/* /*
Shallow clone an object, correctly handling prototype Shallow clone an object, correctly handling prototype
*/ */
export default function _shallowClone(orig) {
export default function _shallowClone<T>(orig: T): T {
return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig); return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig);
} }
@@ -9,94 +9,56 @@ AnnoMatrix stay in sync:
*/ */
import Crossfilter from "../util/typedCrossfilter"; import Crossfilter from "../util/typedCrossfilter";
import { _getColumnSchema } from "./schema"; import { _getColumnSchema } from "./schema";
import {
AnnotationColumnSchema,
Field,
EmbeddingSchema,
} from "../common/types/schema";
import AnnoMatrix from "./annoMatrix";
import {
Dataframe,
DataframeValue,
DataframeValueArray,
LabelType,
} from "../util/dataframe";
import { Query } from "./query";
import { TypedArray } from "../common/types/arraytypes";
import { LabelArray } from "../util/dataframe/types";
type ObsDimensionParams = function _dimensionNameFromDf(field, df) {
| [string, DataframeValueArray, DataframeValueArray]
| [string, DataframeValueArray]
| [string, DataframeValueArray, Int32ArrayConstructor]
| [string, DataframeValueArray, Float32ArrayConstructor];
function _dimensionNameFromDf(field: Field, df: Dataframe): string {
const colNames = df.colIndex.labels(); const colNames = df.colIndex.labels();
return _dimensionName(field, colNames); return _dimensionName(field, colNames);
} }
function _dimensionName( function _dimensionName(field, colNames) {
field: Field,
colNames: LabelType | LabelArray
): string {
if (!Array.isArray(colNames)) return `${field}/${colNames}`; if (!Array.isArray(colNames)) return `${field}/${colNames}`;
return `${field}/${colNames.join(":")}`; return `${field}/${colNames.join(":")}`;
} }
export default class AnnoMatrixObsCrossfilter { export default class AnnoMatrixObsCrossfilter {
annoMatrix: AnnoMatrix; constructor(annoMatrix, _obsCrossfilter = null) {
obsCrossfilter: Crossfilter;
constructor(
annoMatrix: AnnoMatrix,
_obsCrossfilter: Crossfilter | null = null
) {
this.annoMatrix = annoMatrix; this.annoMatrix = annoMatrix;
this.obsCrossfilter = this.obsCrossfilter =
_obsCrossfilter || new Crossfilter(annoMatrix._cache.obs); _obsCrossfilter || new Crossfilter(annoMatrix._cache.obs);
this.obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs); this.obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs);
} }
size(): number { size() {
return this.obsCrossfilter.size(); return this.obsCrossfilter.size();
} }
/** /**
Managing the associated annoMatrix. These wrappers are necessary to Managing the associated annoMatrix. These wrappers are necessary to
make coordinated changes to BOTH the crossfilter and annoMatrix, and make coordinated changes to BOTH the crossfilter and annoMatrix, and
ensure that all state stays synchronized. ensure that all state stays synchronized.
See API documentation in annoMatrix.js. See API documentation in annoMatrix.js.
**/ **/
addObsColumn<T extends DataframeValueArray>( addObsColumn(colSchema, Ctor, value) {
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): AnnoMatrixObsCrossfilter {
const annoMatrix = this.annoMatrix.addObsColumn(colSchema, Ctor, value); const annoMatrix = this.annoMatrix.addObsColumn(colSchema, Ctor, value);
const obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs); const obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs);
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
dropObsColumn(col: LabelType): AnnoMatrixObsCrossfilter { dropObsColumn(col) {
const annoMatrix = this.annoMatrix.dropObsColumn(col); const annoMatrix = this.annoMatrix.dropObsColumn(col);
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
const dimName = _dimensionName(Field.obs, col); const dimName = _dimensionName("obs", col);
if (obsCrossfilter.hasDimension(dimName)) { if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName); obsCrossfilter = obsCrossfilter.delDimension(dimName);
} }
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
renameObsColumn( renameObsColumn(oldCol, newCol) {
oldCol: LabelType,
newCol: LabelType
): AnnoMatrixObsCrossfilter {
const annoMatrix = this.annoMatrix.renameObsColumn(oldCol, newCol); const annoMatrix = this.annoMatrix.renameObsColumn(oldCol, newCol);
const oldDimName = _dimensionName(Field.obs, oldCol); const oldDimName = _dimensionName("obs", oldCol);
const newDimName = _dimensionName(Field.obs, newCol); const newDimName = _dimensionName("obs", newCol);
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(oldDimName)) { if (obsCrossfilter.hasDimension(oldDimName)) {
obsCrossfilter = obsCrossfilter.renameDimension(oldDimName, newDimName); obsCrossfilter = obsCrossfilter.renameDimension(oldDimName, newDimName);
@@ -104,12 +66,9 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
addObsAnnoCategory( addObsAnnoCategory(col, category) {
col: LabelType,
category: string
): AnnoMatrixObsCrossfilter {
const annoMatrix = this.annoMatrix.addObsAnnoCategory(col, category); const annoMatrix = this.annoMatrix.addObsAnnoCategory(col, category);
const dimName = _dimensionName(Field.obs, col); const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) { if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName); obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -117,17 +76,13 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
async removeObsAnnoCategory( async removeObsAnnoCategory(col, category, unassignedCategory) {
col: LabelType,
category: string,
unassignedCategory: string
): Promise<AnnoMatrixObsCrossfilter> {
const annoMatrix = await this.annoMatrix.removeObsAnnoCategory( const annoMatrix = await this.annoMatrix.removeObsAnnoCategory(
col, col,
category, category,
unassignedCategory unassignedCategory
); );
const dimName = _dimensionName(Field.obs, col); const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) { if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName); obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -135,17 +90,13 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
async setObsColumnValues( async setObsColumnValues(col, rowLabels, value) {
col: LabelType,
rowLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrixObsCrossfilter> {
const annoMatrix = await this.annoMatrix.setObsColumnValues( const annoMatrix = await this.annoMatrix.setObsColumnValues(
col, col,
rowLabels, rowLabels,
value value
); );
const dimName = _dimensionName(Field.obs, col); const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) { if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName); obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -153,17 +104,13 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
async resetObsColumnValues<T extends DataframeValue>( async resetObsColumnValues(col, oldValue, newValue) {
col: LabelType,
oldValue: T,
newValue: T
): Promise<AnnoMatrixObsCrossfilter> {
const annoMatrix = await this.annoMatrix.resetObsColumnValues( const annoMatrix = await this.annoMatrix.resetObsColumnValues(
col, col,
oldValue, oldValue,
newValue newValue
); );
const dimName = _dimensionName(Field.obs, col); const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) { if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName); obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -171,25 +118,23 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrixObsCrossfilter { addEmbedding(colSchema) {
const annoMatrix = this.annoMatrix.addEmbedding(colSchema); const annoMatrix = this.annoMatrix.addEmbedding(colSchema);
return new AnnoMatrixObsCrossfilter(annoMatrix, this.obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, this.obsCrossfilter);
} }
/** /**
* Drop the crossfilter dimension. Do not change the annoMatrix. Useful when we * Drop the crossfilter dimension. Do not change the annoMatrix. Useful when we
* want to stop tracking the selection state, but aren't sure we want to blow the * want to stop trackin the selection state, but aren't sure we want to blow the
* annomatrix cache. * annomatrix cache.
*/ */
dropDimension(field: Field, query: Query): AnnoMatrixObsCrossfilter { dropDimension(field, query) {
const { annoMatrix } = this; const { annoMatrix } = this;
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
const keys = annoMatrix const keys = annoMatrix
.getCacheKeys(field, query) .getCacheKeys(field, query)
// @ts-expect-error ts-migrate --- suppressing TS defect (https://github.com/microsoft/TypeScript/issues/44373). .filter((k) => k !== undefined);
// Compiler is complaining that expression is not callable on array union types. Remove suppression once fixed. const dimName = _dimensionName(field, keys);
.filter((k?: string | number) => k !== undefined);
const dimName = _dimensionName(field, keys as string[]);
if (obsCrossfilter.hasDimension(dimName)) { if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName); obsCrossfilter = obsCrossfilter.delDimension(dimName);
} }
@@ -201,12 +146,7 @@ export default class AnnoMatrixObsCrossfilter {
are just wrappers to lazy create indices. are just wrappers to lazy create indices.
**/ **/
async select( async select(field, query, spec) {
field: Field,
query: Query,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from util/typedCrossfilter
spec: any
): Promise<AnnoMatrixObsCrossfilter> {
const { annoMatrix } = this; const { annoMatrix } = this;
let { obsCrossfilter } = this; let { obsCrossfilter } = this;
@@ -219,9 +159,7 @@ export default class AnnoMatrixObsCrossfilter {
// grab the data, so we can grab the index. // grab the data, so we can grab the index.
const df = await annoMatrix.fetch(field, query); const df = await annoMatrix.fetch(field, query);
if (!df) {
throw new Error("Dataframe cannot be `undefined`");
}
const dimName = _dimensionNameFromDf(field, df); const dimName = _dimensionNameFromDf(field, df);
if (!obsCrossfilter.hasDimension(dimName)) { if (!obsCrossfilter.hasDimension(dimName)) {
// lazy index generation - add dimension when first used // lazy index generation - add dimension when first used
@@ -238,26 +176,23 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
selectAll(): AnnoMatrixObsCrossfilter { selectAll() {
/* /*
Select all on any dimension in this field. Select all on any dimension in this field.
*/ */
const { annoMatrix } = this; const { annoMatrix } = this;
const currentDims = this.obsCrossfilter.dimensionNames(); const currentDims = this.obsCrossfilter.dimensionNames();
const obsCrossfilter = currentDims.reduce( const obsCrossfilter = currentDims.reduce((xfltr, dim) => xfltr.select(dim, { mode: "all" }), this.obsCrossfilter);
(xfltr, dim) => xfltr.select(dim, { mode: "all" }),
this.obsCrossfilter
);
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
} }
countSelected(): number { countSelected() {
/* if no data yet indexed in the crossfilter, just say everything is selected */ /* if no data yet indexed in the crossfilter, just say everything is selected */
if (this.obsCrossfilter.size() === 0) return this.annoMatrix.nObs; if (this.obsCrossfilter.size() === 0) return this.annoMatrix.nObs;
return this.obsCrossfilter.countSelected(); return this.obsCrossfilter.countSelected();
} }
allSelectedMask(): Uint8Array { allSelectedMask() {
/* if no data yet indexed in the crossfilter, just say everything is selected */ /* if no data yet indexed in the crossfilter, just say everything is selected */
if ( if (
this.obsCrossfilter.size() === 0 || this.obsCrossfilter.size() === 0 ||
@@ -269,7 +204,7 @@ export default class AnnoMatrixObsCrossfilter {
return this.obsCrossfilter.allSelectedMask(); return this.obsCrossfilter.allSelectedMask();
} }
allSelectedLabels(): LabelArray { allSelectedLabels() {
/* if no data yet indexed in the crossfilter, just say everything is selected */ /* if no data yet indexed in the crossfilter, just say everything is selected */
if ( if (
this.obsCrossfilter.size() === 0 || this.obsCrossfilter.size() === 0 ||
@@ -283,18 +218,12 @@ export default class AnnoMatrixObsCrossfilter {
return index.labels(); return index.labels();
} }
fillByIsSelected<A extends TypedArray>( fillByIsSelected(array, selectedValue, deselectedValue) {
array: A,
selectedValue: A[0],
deselectedValue: A[0]
): A {
/* if no data yet indexed in the crossfilter, just say everything is selected */ /* if no data yet indexed in the crossfilter, just say everything is selected */
if ( if (
this.obsCrossfilter.size() === 0 || this.obsCrossfilter.size() === 0 ||
this.obsCrossfilter.dimensionNames().length === 0 this.obsCrossfilter.dimensionNames().length === 0
) { ) {
// @ts-expect-error ts-migrate --- TODO revisit:
// Type 'Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array' is not assignable to type 'A'...
return array.fill(selectedValue); return array.fill(selectedValue);
} }
return this.obsCrossfilter.fillByIsSelected( return this.obsCrossfilter.fillByIsSelected(
@@ -308,35 +237,25 @@ export default class AnnoMatrixObsCrossfilter {
** Private below ** Private below
**/ **/
_addObsCrossfilterDimension( _addObsCrossfilterDimension(annoMatrix, obsCrossfilter, field, df) {
annoMatrix: AnnoMatrix,
obsCrossfilter: Crossfilter,
field: Field,
df: Dataframe
): Crossfilter {
if (field === "var") return obsCrossfilter; if (field === "var") return obsCrossfilter;
const dimName = _dimensionNameFromDf(field, df); const dimName = _dimensionNameFromDf(field, df);
const dimParams = this._getObsDimensionParams(field, df); const dimParams = this._getObsDimensionParams(field, df);
obsCrossfilter = obsCrossfilter.setData(annoMatrix._cache.obs); obsCrossfilter = obsCrossfilter.setData(annoMatrix._cache.obs);
// @ts-expect-error ts-migrate --- TODO revisit:
// `...dimParams`: A spread argument must either have a tuple type or be passed to a rest parameter.
obsCrossfilter = obsCrossfilter.addDimension(dimName, ...dimParams); obsCrossfilter = obsCrossfilter.addDimension(dimName, ...dimParams);
return obsCrossfilter; return obsCrossfilter;
} }
_getColumnBaseType(field: Field, col: LabelType): string { _getColumnBaseType(field, col) {
/* Look up the primitive type for this field/col */ /* Look up the primitive type for this field/col */
const colSchema = _getColumnSchema(this.annoMatrix.schema, field, col); const colSchema = _getColumnSchema(this.annoMatrix.schema, field, col);
return colSchema.type; return colSchema.type;
} }
_getObsDimensionParams( _getObsDimensionParams(field, df) {
field: Field,
df: Dataframe
): ObsDimensionParams | undefined {
/* return the crossfilter dimensiontype type and params for this field/dataframe */ /* return the crossfilter dimensiontype type and params for this field/dataframe */
if (field === Field.emb) { if (field === "emb") {
/* assumed to be 2D */ /* assumed to be 2D */
return ["spatial", df.icol(0).asArray(), df.icol(1).asArray()]; return ["spatial", df.icol(0).asArray(), df.icol(1).asArray()];
} }
@@ -344,8 +263,6 @@ export default class AnnoMatrixObsCrossfilter {
/* assumed to be 1D */ /* assumed to be 1D */
const col = df.icol(0); const col = df.icol(0);
const colName = df.colIndex.getLabel(0); const colName = df.colIndex.getLabel(0);
// @ts-expect-error --- TODO revisit:
// `colName` Argument of type 'LabelType | undefined' is not assignable to parameter of type 'LabelType'. Type 'undefined' is not assignable to type 'LabelType'.
const type = this._getColumnBaseType(field, colName); const type = this._getColumnBaseType(field, colName);
if (type === "string" || type === "categorical" || type === "boolean") { if (type === "string" || type === "categorical" || type === "boolean") {
return ["enum", col.asArray()]; return ["enum", col.asArray()];
+25
View File
@@ -0,0 +1,25 @@
export { doBinaryRequest, doFetch } from "../util/actionHelpers";
/* double URI encode - needed for query-param filters */
export function _dubEncURIComp(s) {
return encodeURIComponent(encodeURIComponent(s));
}
/* currently unused, consider deleting */
export function _fetchResult(promise) {
let _status = "pending";
const res = promise.then(
(r) => {
_status = "success";
return r;
},
(e) => {
_status = "error";
throw e;
}
);
res.status = () => _status;
return res;
}
-28
View File
@@ -1,28 +0,0 @@
export { doBinaryRequest, doFetch } from "../util/actionHelpers";
/* double URI encode - needed for query-param filters */
export function _dubEncURIComp(s: string | number | boolean): string {
return encodeURIComponent(encodeURIComponent(s));
}
/* currently unused, consider deleting */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function _fetchResult(promise: any) {
let _status = "pending";
const res = promise.then(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(r: any) => {
_status = "success";
return r;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(e: any) => {
_status = "error";
throw e;
}
);
res.status = () => _status;
return res;
}
@@ -2,47 +2,31 @@ import { doBinaryRequest, doFetch } from "./fetchHelpers";
import { matrixFBSToDataframe } from "../util/stateManager/matrix"; import { matrixFBSToDataframe } from "../util/stateManager/matrix";
import { _getColumnSchema } from "./schema"; import { _getColumnSchema } from "./schema";
import { import {
addObsAnnoCategory,
addObsAnnoColumn, addObsAnnoColumn,
addObsLayout,
removeObsAnnoCategory,
removeObsAnnoColumn, removeObsAnnoColumn,
addObsAnnoCategory,
removeObsAnnoCategory,
addObsLayout,
} from "../util/stateManager/schemaHelpers"; } from "../util/stateManager/schemaHelpers";
import { isAnyArray } from "../common/types/arraytypes"; import { isArrayOrTypedArray } from "../util/typeHelpers";
import { _whereCacheCreate, WhereCache } from "./whereCache"; import { _whereCacheCreate } from "./whereCache";
import AnnoMatrix from "./annoMatrix"; import AnnoMatrix from "./annoMatrix";
import PromiseLimit from "../util/promiseLimit"; import PromiseLimit from "../util/promiseLimit";
import { import {
_expectComplexQuery,
_expectSimpleQuery, _expectSimpleQuery,
_hashStringValues, _expectComplexQuery,
_urlEncodeComplexQuery,
_urlEncodeLabelQuery, _urlEncodeLabelQuery,
ComplexQuery, _urlEncodeComplexQuery,
Query, _hashStringValues,
} from "./query"; } from "./query";
import { import {
normalizeResponse, normalizeResponse,
normalizeWritableCategoricalSchema, normalizeWritableCategoricalSchema,
} from "./normalize"; } from "./normalize";
import {
AnnotationColumnSchema,
Field,
EmbeddingSchema,
RawSchema,
} from "../common/types/schema";
import {
Dataframe,
DataframeValue,
DataframeValueArray,
LabelType,
} from "../util/dataframe";
const promiseThrottle = new PromiseLimit(5); const promiseThrottle = new PromiseLimit(5);
export default class AnnoMatrixLoader extends AnnoMatrix { export default class AnnoMatrixLoader extends AnnoMatrix {
baseURL: string;
/* /*
AnnoMatrix implementation which proxies to HTTP server using the CXG REST API. AnnoMatrix implementation which proxies to HTTP server using the CXG REST API.
Used as the base (non-view) instance. Used as the base (non-view) instance.
@@ -53,7 +37,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
new AnnoMatrixLoader(serverBaseURL, schema) -> instance new AnnoMatrixLoader(serverBaseURL, schema) -> instance
*/ */
constructor(baseURL: string, schema: RawSchema) { constructor(baseURL, schema) {
const { nObs, nVar } = schema.dataframe; const { nObs, nVar } = schema.dataframe;
super(schema, nObs, nVar); super(schema, nObs, nVar);
@@ -68,36 +52,24 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
/** /**
** Public. API described in base class. ** Public. API described in base class.
**/ **/
addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix { addObsAnnoCategory(col, category) {
/* /*
Add a new category (aka label) to the schema for an obs column. Add a new category (aka label) to the schema for an obs column.
*/ */
const colSchema = _getColumnSchema( const colSchema = _getColumnSchema(this.schema, "obs", col);
this.schema, _writableCategoryTypeCheck(colSchema); // throws on error
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.schema = addObsAnnoCategory(this.schema, col, category); newAnnoMatrix.schema = addObsAnnoCategory(this.schema, col, category);
return newAnnoMatrix; return newAnnoMatrix;
} }
async removeObsAnnoCategory( async removeObsAnnoCategory(col, category, unassignedCategory) {
col: LabelType,
category: string,
unassignedCategory: string
): Promise<AnnoMatrix> {
/* /*
Remove a single "category" (aka "label") from the data & schema of an obs column. Remove a single "category" (aka "label") from the data & schema of an obs column.
*/ */
const colSchema = _getColumnSchema( const colSchema = _getColumnSchema(this.schema, "obs", col);
this.schema, _writableCategoryTypeCheck(colSchema); // throws on error
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
const newAnnoMatrix = await this.resetObsColumnValues( const newAnnoMatrix = await this.resetObsColumnValues(
col, col,
@@ -112,16 +84,12 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
dropObsColumn(col: LabelType): AnnoMatrix { dropObsColumn(col) {
/* /*
drop column from field drop column from field
*/ */
const colSchema = _getColumnSchema( const colSchema = _getColumnSchema(this.schema, "obs", col);
this.schema, _writableCheck(colSchema); // throws on error
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCheck(colSchema); // throws on error
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col); newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col);
@@ -129,11 +97,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
addObsColumn<T extends DataframeValueArray>( addObsColumn(colSchema, Ctor, value) {
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): AnnoMatrix {
/* /*
add a column to field, initializing with value. Value may add a column to field, initializing with value. Value may
be one of: be one of:
@@ -144,7 +108,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
colSchema.writable = true; colSchema.writable = true;
const colName = colSchema.name; const colName = colSchema.name;
if ( if (
_getColumnSchema(this.schema, Field.obs, colName) || _getColumnSchema(this.schema, "obs", colName) ||
this._cache.obs.hasCol(colName) this._cache.obs.hasCol(colName)
) { ) {
throw new Error("column already exists"); throw new Error("column already exists");
@@ -152,7 +116,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
let data; let data;
if (isAnyArray(value)) { if (isArrayOrTypedArray(value)) {
if (value.constructor !== Ctor) if (value.constructor !== Ctor)
throw new Error("Mismatched value array type"); throw new Error("Mismatched value array type");
if (value.length !== this.nObs) if (value.length !== this.nObs)
@@ -170,50 +134,35 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix { renameObsColumn(oldCol, newCol) {
/* /*
Rename the obs oldColName to newColName. oldCol must be writable. Rename the obs oldColName to newColName. oldCol must be writable.
*/ */
const oldColSchema = _getColumnSchema( const oldColSchema = _getColumnSchema(this.schema, "obs", oldCol);
this.schema, _writableCheck(oldColSchema); // throws on error
Field.obs,
oldCol
) as AnnotationColumnSchema;
_writableObsCheck(oldColSchema);
const value = this._cache.obs.hasCol(oldCol) const value = this._cache.obs.hasCol(oldCol)
? this._cache.obs.col(oldCol).asArray() ? this._cache.obs.col(oldCol).asArray()
: undefined; : undefined;
return this.dropObsColumn(oldCol).addObsColumn( return this.dropObsColumn(oldCol).addObsColumn(
{ {
...oldColSchema, ...oldColSchema,
// @ts-expect-error ts-migrate --- TODO revisit:
// `name`: Type 'LabelType' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'.
name: newCol, name: newCol,
}, },
// @ts-expect-error ts-migrate --- TODO revisit:
// `value`: Object is possibly 'undefined'.
value.constructor, value.constructor,
value value
); );
} }
async setObsColumnValues( async setObsColumnValues(col, rowLabels, value) {
col: LabelType,
rowLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrix> {
/* /*
Set all rows identified by rowLabels to value. Set all rows identified by rowLabels to value.
*/ */
const colSchema = _getColumnSchema( const colSchema = _getColumnSchema(this.schema, "obs", col);
this.schema, _writableCategoryTypeCheck(colSchema); // throws on error
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
// ensure that we have the data in cache before we manipulate it // ensure that we have the data in cache before we manipulate it
await this.fetch(Field.obs, col); await this.fetch("obs", col);
if (!this._cache.obs.hasCol(col)) if (!this._cache.obs.hasCol(col))
throw new Error("Internal error - user annotation data missing"); throw new Error("Internal error - user annotation data missing");
@@ -221,7 +170,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
const data = this._cache.obs.col(col).asArray().slice(); const data = this._cache.obs.col(col).asArray().slice();
for (let i = 0, len = rowIndices.length; i < len; i += 1) { for (let i = 0, len = rowIndices.length; i < len; i += 1) {
const idx = rowIndices[i]; const idx = rowIndices[i];
if (idx === -1) throw new Error("Unknown row label"); if (idx === undefined) throw new Error("Unknown row label");
data[idx] = value; data[idx] = value;
} }
@@ -234,29 +183,19 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
async resetObsColumnValues<T extends DataframeValue>( async resetObsColumnValues(col, oldValue, newValue) {
col: LabelType,
oldValue: T,
newValue: T
): Promise<AnnoMatrix> {
/* /*
Set all rows with value 'oldValue' to 'newValue'. Set all rows with value 'oldValue' to 'newValue'.
*/ */
const colSchema = _getColumnSchema( const colSchema = _getColumnSchema(this.schema, "obs", col);
this.schema, _writableCategoryTypeCheck(colSchema); // throws on error
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
// @ts-expect-error ts-migrate --- TODO revisit:
// `colSchema.categories`: Object is possibly 'undefined'.
if (!colSchema.categories.includes(oldValue)) { if (!colSchema.categories.includes(oldValue)) {
throw new Error("unknown category"); throw new Error("unknown category");
} }
// ensure that we have the data in cache before we manipulate it // ensure that we have the data in cache before we manipulate it
await this.fetch(Field.obs, col); await this.fetch("obs", col);
if (!this._cache.obs.hasCol(col)) if (!this._cache.obs.hasCol(col))
throw new Error("Internal error - user annotation data missing"); throw new Error("Internal error - user annotation data missing");
@@ -274,12 +213,12 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix { addEmbedding(colSchema) {
/* /*
add new layout to the obs embeddings add new layout to the obs embeddings
*/ */
const { name: colName } = colSchema; const { name: colName } = colSchema;
if (_getColumnSchema(this.schema, Field.emb, colName)) { if (_getColumnSchema(this.schema, "emb", colName)) {
throw new Error("column already exists"); throw new Error("column already exists");
} }
@@ -291,10 +230,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
/** /**
** Private below ** Private below
**/ **/
async _doLoad( async _doLoad(field, query) {
field: Field,
query: Query
): Promise<[WhereCache | null, Dataframe]> {
/* /*
_doLoad - evaluates the query against the field. Returns: _doLoad - evaluates the query against the field. Returns:
* whereCache update: column query map mapping the query to the column labels * whereCache update: column query map mapping the query to the column labels
@@ -321,9 +257,8 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
default: default:
throw new Error("Unknown field name"); throw new Error("Unknown field name");
} }
const buffer = await promiseThrottle.priorityAdd(priority, doRequest); const buffer = await promiseThrottle.priorityAdd(priority, doRequest);
// @ts-expect-error --- TODO revisit:
// `buffer`: Argument of type 'unknown' is not assignable to parameter of type 'ArrayBuffer | ArrayBuffer[]'. Type 'unknown' is not assignable to type 'ArrayBuffer[]'.
let result = matrixFBSToDataframe(buffer); let result = matrixFBSToDataframe(buffer);
if (!result || result.isEmpty()) throw Error("Unknown field/col"); if (!result || result.isEmpty()) throw Error("Unknown field/col");
@@ -333,7 +268,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
result.colIndex.labels() result.colIndex.labels()
); );
result = normalizeResponse(field, this.schema, result); result = normalizeResponse(field, query, this.schema, result);
return [whereCacheUpdate, result]; return [whereCacheUpdate, result];
} }
@@ -343,26 +278,20 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
Utility functions below Utility functions below
*/ */
function _writableObsCheck(obsColSchema: AnnotationColumnSchema): void { function _writableCheck(colSchema) {
if (!obsColSchema?.writable) { if (!colSchema?.writable) {
throw new Error("Unknown or readonly obs column"); throw new Error("Unknown or readonly obs column");
} }
} }
function _writableObsCategoryTypeCheck( function _writableCategoryTypeCheck(colSchema) {
obsColSchema: AnnotationColumnSchema _writableCheck(colSchema);
): void { if (colSchema.type !== "categorical") {
_writableObsCheck(obsColSchema);
if (obsColSchema.type !== "categorical") {
throw new Error("column must be categorical"); throw new Error("column must be categorical");
} }
} }
function _embLoader( function _embLoader(baseURL, _field, query) {
baseURL: string,
_field: Field,
query: Query
): () => Promise<ArrayBuffer> {
_expectSimpleQuery(query); _expectSimpleQuery(query);
const urlBase = `${baseURL}layout/obs`; const urlBase = `${baseURL}layout/obs`;
@@ -371,11 +300,7 @@ function _embLoader(
return () => doBinaryRequest(url); return () => doBinaryRequest(url);
} }
function _obsOrVarLoader( function _obsOrVarLoader(baseURL, field, query) {
baseURL: string,
field: Field,
query: Query
): () => Promise<ArrayBuffer> {
_expectSimpleQuery(query); _expectSimpleQuery(query);
const urlBase = `${baseURL}annotations/${field}`; const urlBase = `${baseURL}annotations/${field}`;
@@ -384,26 +309,19 @@ function _obsOrVarLoader(
return () => doBinaryRequest(url); return () => doBinaryRequest(url);
} }
function _XLoader( function _XLoader(baseURL, field, query) {
baseURL: string,
_field: Field,
query: Query
): () => Promise<ArrayBuffer> {
_expectComplexQuery(query); _expectComplexQuery(query);
// Casting here as query is validated to be complex in _expectComplexQuery above. if (query.where) {
const complexQuery = query as ComplexQuery;
if ("where" in complexQuery) {
const urlBase = `${baseURL}data/var`; const urlBase = `${baseURL}data/var`;
const urlQuery = _urlEncodeComplexQuery(complexQuery); const urlQuery = _urlEncodeComplexQuery(query);
const url = `${urlBase}?${urlQuery}`; const url = `${urlBase}?${urlQuery}`;
return () => doBinaryRequest(url); return () => doBinaryRequest(url);
} }
if ("summarize" in complexQuery) { if (query.summarize) {
const urlBase = `${baseURL}summarize/var`; const urlBase = `${baseURL}summarize/var`;
const urlQuery = _urlEncodeComplexQuery(complexQuery); const urlQuery = _urlEncodeComplexQuery(query);
if (urlBase.length + urlQuery.length < 2000) { if (urlBase.length + urlQuery.length < 2000) {
const url = `${urlBase}?${urlQuery}`; const url = `${urlBase}?${urlQuery}`;
@@ -11,24 +11,16 @@ Undoable metareducer and the AnnoMatrix private API. It would be helpful
to make the Undoable interface better factored. to make the Undoable interface better factored.
*/ */
import { Action, Dispatch, MiddlewareAPI } from "redux"; const annoMatrixGC = (store) => (next) => (action) => {
import AnnoMatrix from "./annoMatrix"; if (_itIsTimeForGC()) {
import { GCHints } from "../common/types/entities"; _doGC(store);
}
const annoMatrixGC = return next(action);
(store: MiddlewareAPI) => };
// GC middleware doesn't add any extra types to dispatch; it just executes GC and continues.
(next: Dispatch) =>
(action: Action): Action => {
if (_itIsTimeForGC()) {
_doGC(store);
}
return next(action);
};
let lastGCTime = 0; let lastGCTime = 0;
const InterGCDelayMS = 30 * 1000; // 30 seconds const InterGCDelayMS = 30 * 1000; // 30 seconds
function _itIsTimeForGC(): boolean { function _itIsTimeForGC() {
/* /*
we don't want to run GC on every dispatch, so throttle it a bit. we don't want to run GC on every dispatch, so throttle it a bit.
@@ -42,22 +34,17 @@ function _itIsTimeForGC(): boolean {
return false; return false;
} }
function _doGC(store: MiddlewareAPI): void { function _doGC(store) {
const state = store.getState(); const state = store.getState();
// these should probably be a function imported from undoable.js, etc, as // these should probably be a function imported from undoable.js, etc, as
// they have overly intimate knowledge of our reducers. // they have overly intimiate knowledge of our reducers.
const undoablePast = state["@@undoable/past"]; const undoablePast = state["@@undoable/past"];
const undoableFuture = state["@@undoable/future"]; const undoableFuture = state["@@undoable/future"];
const undoableStack = undoablePast const undoableStack = undoablePast
.concat(undoableFuture) .concat(undoableFuture)
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers .flatMap((snapshot) =>
.flatMap((snapshot: any) => snapshot.filter((v) => v[0] === "annoMatrix").map((v) => v[1])
snapshot
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers
.filter((v: any) => v[0] === "annoMatrix")
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers
.map((v: any) => v[1])
); );
const currentAnnoMatrix = state.annoMatrix; const currentAnnoMatrix = state.annoMatrix;
@@ -65,17 +52,15 @@ function _doGC(store: MiddlewareAPI): void {
We want to identify those matrixes currently "hot", ie, linked from the current annoMatrix, We want to identify those matrixes currently "hot", ie, linked from the current annoMatrix,
as our current gc algo is more aggressive with those not hot. as our current gc algo is more aggressive with those not hot.
*/ */
const allAnnoMatrices = new Map<AnnoMatrix, GCHints>( const allAnnoMatrices = new Map(
undoableStack.map((m: AnnoMatrix) => [m, { isHot: false }]) undoableStack.map((m) => [m, { isHot: false }])
); );
let am = currentAnnoMatrix; let am = currentAnnoMatrix;
while (am?.isView) { while (am) {
allAnnoMatrices.set(am, { isHot: true }); allAnnoMatrices.set(am, { isHot: true });
am = am.viewOf; am = am.viewOf;
} }
allAnnoMatrices.forEach((hints, annoMatrix: AnnoMatrix) => allAnnoMatrices.forEach((hints, annoMatrix) => annoMatrix._gc(hints));
annoMatrix._gc(hints)
);
} }
export default annoMatrixGC; export default annoMatrixGC;
@@ -5,19 +5,8 @@ import {
overflowCategoryLabel, overflowCategoryLabel,
globalConfig, globalConfig,
} from "../globals"; } from "../globals";
import { Dataframe, LabelType, DataframeColumn } from "../util/dataframe";
import {
AnnotationColumnSchema,
ArraySchema,
Field,
Schema,
} from "../common/types/schema";
export function normalizeResponse( export function normalizeResponse(field, query, schema, response) {
field: Field,
schema: Schema,
response: Dataframe
): Dataframe {
/** /**
* There are a number of assumptions in the front-end about data typing and data * There are a number of assumptions in the front-end about data typing and data
* characteristics. This routine will normalize a server response dataframe * characteristics. This routine will normalize a server response dataframe
@@ -42,15 +31,11 @@ export function normalizeResponse(
*/ */
// currently no data or schema normalization necessary for X or emb // currently no data or schema normalization necessary for X or emb
if (field !== Field.obs && field !== Field.var) return response; if (field !== "obs" && field !== "var") return response;
const colLabels = response.colIndex.labels(); const colLabels = response.colIndex.labels();
for (const colLabel of colLabels) { for (const colLabel of colLabels) {
const colSchema = _getColumnSchema( const colSchema = _getColumnSchema(schema, field, colLabel);
schema,
field,
colLabel
) as AnnotationColumnSchema;
const isIndex = _isIndex(schema, field, colLabel); const isIndex = _isIndex(schema, field, colLabel);
const { type, writable } = colSchema; const { type, writable } = colSchema;
@@ -74,7 +59,7 @@ export function normalizeResponse(
return response; return response;
} }
function castColumnToBoolean(df: Dataframe, label: LabelType): Dataframe { function castColumnToBoolean(df, label) {
const colData = df.col(label).asArray(); const colData = df.col(label).asArray();
const newColData = new Array(colData.length); const newColData = new Array(colData.length);
for (let i = 0; i < colData.length; i += 1) newColData[i] = !!colData[i]; for (let i = 0; i < colData.length; i += 1) newColData[i] = !!colData[i];
@@ -82,16 +67,13 @@ function castColumnToBoolean(df: Dataframe, label: LabelType): Dataframe {
return df; return df;
} }
export function normalizeWritableCategoricalSchema( export function normalizeWritableCategoricalSchema(colSchema, col) {
colSchema: AnnotationColumnSchema, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
col: DataframeColumn
): ArraySchema {
/* /*
Ensure all enum writable / categorical schema have a categories array, that Ensure all enum writable / categorical schema have a categories array, that
the categories array contains all unique values in the data array, AND that the categories array contains all unique values in the data array, AND that
the array is UI sorted. the array is UI sorted.
*/ */
const categorySet = new Set<string>( const categorySet = new Set(
col.summarizeCategorical().categories.concat(colSchema.categories ?? []) col.summarizeCategorical().categories.concat(colSchema.categories ?? [])
); );
if (!categorySet.has(unassignedCategoryLabel)) { if (!categorySet.has(unassignedCategoryLabel)) {
@@ -101,19 +83,15 @@ export function normalizeWritableCategoricalSchema(
return colSchema; return colSchema;
} }
export function normalizeCategorical( export function normalizeCategorical(df, colLabel, colSchema) {
df: Dataframe,
colLabel: LabelType,
colSchema: AnnotationColumnSchema
): Dataframe {
/* /*
If writable, ensure schema matches data and we have an unassigned label If writable, ensure schema matches data and we have an unassigned label
If not writable, ensure schema matches data and that we consolidate labels in excess If not writable, ensure schema matches data and that we consolidate labels in excess
of "top N" into an overflow labels. of "top N" into an overflow labels.
*/ */
const { writable } = colSchema; const { writable } = colSchema;
const col = df.col(colLabel); const col = df.col(colLabel);
if (writable) { if (writable) {
// writable (aka user) annotations // writable (aka user) annotations
normalizeWritableCategoricalSchema(colSchema, col); normalizeWritableCategoricalSchema(colSchema, col);
@@ -125,7 +103,7 @@ export function normalizeCategorical(
// consolidate all categories from data and schema into a single list // consolidate all categories from data and schema into a single list
const colDataSummary = col.summarizeCategorical(); const colDataSummary = col.summarizeCategorical();
const allCategories = new Set<string>( const allCategories = new Set(
colDataSummary.categories.concat(colSchema.categories ?? []) colDataSummary.categories.concat(colSchema.categories ?? [])
); );
@@ -1,52 +1,21 @@
import sha1 from "sha1"; import sha1 from "sha1";
import { _dubEncURIComp } from "./fetchHelpers"; import { _dubEncURIComp } from "./fetchHelpers";
import { Field } from "../common/types/schema";
import { LabelType } from "../util/dataframe";
/** /**
* Query utilities, mostly for debugging support and validation. * Query utilities, mostly for debugging support and validation.
*/ */
export type ComplexQuery = SummarizeQuery | WhereQuery;
export type Query = LabelType | ComplexQuery;
interface SummarizeQuery {
summarize: SummarizeQueryTerm;
}
interface SummarizeQueryTerm {
column: string;
field: string;
method: string;
values: string[];
}
interface WhereQuery {
where: WhereQueryTerm;
}
interface WhereQueryTerm {
column: string;
field: string;
value: string;
}
export function _expectSimpleQuery(query: Query): void {
if (typeof query === "object") throw new Error("expected simple query");
}
/** /**
* Normalize & error check the query. * Normalize & error check the query.
* @param {Query} query - the query * @param {object | string} query - the query
* @returns {Query} - the normalized query * @returns {object | string} - the normalized query
*/ */
export function _queryValidate(query: Query): Query { export function _queryValidate(query) {
if (typeof query !== "object") return query; if (typeof query !== "object") return query;
if ("where" in query && "summarize" in query) if (query.where && query.summarize)
throw new Error("query may not specify both where and summarize"); throw new Error("query may not specify both where and summarize");
if ("where" in query) { if (query.where) {
const { const {
field: queryField, field: queryField,
column: queryColumn, column: queryColumn,
@@ -56,7 +25,7 @@ export function _queryValidate(query: Query): Query {
throw new Error("Incomplete where query"); throw new Error("Incomplete where query");
return query; return query;
} }
if ("summarize" in query) { if (query.summarize) {
const { const {
field: queryField, field: queryField,
column: queryColumn, column: queryColumn,
@@ -71,7 +40,11 @@ export function _queryValidate(query: Query): Query {
throw new Error("query must specify one of where or summarize"); throw new Error("query must specify one of where or summarize");
} }
export function _expectComplexQuery(query: Query): void { export function _expectSimpleQuery(query) {
if (typeof query === "object") throw new Error("expected simple query");
}
export function _expectComplexQuery(query) {
if (typeof query !== "object") throw new Error("expected complex query"); if (typeof query !== "object") throw new Error("expected complex query");
} }
@@ -80,12 +53,12 @@ export function _expectComplexQuery(query: Query): void {
* *
* @param {string} field * @param {string} field
* @param {string|object} query * @param {string|object} query
* @returns {string} the key * @returns the key
*/ */
export function _queryCacheKey(field: Field, query: Query): string { export function _queryCacheKey(field, query) {
if (typeof query === "object") { if (typeof query === "object") {
// complex query // complex query
if ("where" in query) { if (query.where) {
const { const {
field: queryField, field: queryField,
column: queryColumn, column: queryColumn,
@@ -93,7 +66,7 @@ export function _queryCacheKey(field: Field, query: Query): string {
} = query.where; } = query.where;
return `${field}/${queryField}/${queryColumn}/${queryValue}`; return `${field}/${queryField}/${queryColumn}/${queryValue}`;
} }
if ("summarize" in query) { if (query.summarize) {
const { const {
method, method,
field: queryField, field: queryField,
@@ -111,34 +84,34 @@ export function _queryCacheKey(field: Field, query: Query): string {
return `${field}/${query}`; return `${field}/${query}`;
} }
function _urlEncodeWhereQuery(q: WhereQueryTerm): string { function _urlEncodeWhereQuery(q) {
const { field: queryField, column: queryColumn, value: queryValue } = q; const { field: queryField, column: queryColumn, value: queryValue } = q;
return `${_dubEncURIComp(queryField)}:${_dubEncURIComp( return `${_dubEncURIComp(queryField)}:${_dubEncURIComp(
queryColumn queryColumn
)}=${_dubEncURIComp(queryValue)}`; )}=${_dubEncURIComp(queryValue)}`;
} }
function _urlEncodeSummarizeQuery(q: SummarizeQueryTerm): string { function _urlEncodeSummarizeQuery(q) {
const { method, field, column, values } = q; const { method, field, column, values } = q;
const filter = values const filter = values
.map((value: string) => _urlEncodeWhereQuery({ field, column, value })) .map((value) => _urlEncodeWhereQuery({ field, column, value }))
.join("&"); .join("&");
return `method=${method}&${filter}`; return `method=${method}&${filter}`;
} }
export function _urlEncodeComplexQuery(q: ComplexQuery): string { export function _urlEncodeComplexQuery(q) {
if (typeof q === "object") { if (typeof q === "object") {
if ("where" in q) { if (q.where) {
return _urlEncodeWhereQuery(q.where); return _urlEncodeWhereQuery(q.where);
} }
if ("summarize" in q) { if (q.summarize) {
return _urlEncodeSummarizeQuery(q.summarize); return _urlEncodeSummarizeQuery(q.summarize);
} }
} }
throw new Error("Unrecognized complex query type"); throw new Error("Unrecognized complex query type");
} }
export function _urlEncodeLabelQuery(colKey: string, q: Query): string { export function _urlEncodeLabelQuery(colKey, q) {
if (!colKey) throw new Error("Unsupported query by name"); if (!colKey) throw new Error("Unsupported query by name");
if (typeof q !== "string") throw new Error("Query must be a simple label."); if (typeof q !== "string") throw new Error("Query must be a simple label.");
return `${colKey}=${encodeURIComponent(q)}`; return `${colKey}=${encodeURIComponent(q)}`;
@@ -147,6 +120,7 @@ export function _urlEncodeLabelQuery(colKey: string, q: Query): string {
/** /**
* Generate the column key the server will send us for this query. * Generate the column key the server will send us for this query.
*/ */
export function _hashStringValues(arrayOfString: string[]): string { export function _hashStringValues(arrayOfString) {
return sha1(arrayOfString.join("")); const hash = sha1(arrayOfString.join(""));
return hash;
} }
@@ -1,54 +1,34 @@
/* /*
Private helper functions related to schema Private helper functions related to schema
*/ */
import { export function _getColumnSchema(schema, field, col) {
AnnotationColumnSchema,
ArraySchema,
Field,
Schema,
} from "../common/types/schema";
import { LabelArray, LabelType } from "../util/dataframe/types";
export function _getColumnSchema(
schema: Schema,
field: Field,
col: LabelType
): ArraySchema {
/* look up the column definition */ /* look up the column definition */
switch (field) { switch (field) {
case Field.obs: case "obs":
if (typeof col === "object") if (typeof col === "object")
throw new Error("unable to get column schema by query"); throw new Error("unable to get column schema by query");
return schema.annotations.obsByName[col]; return schema.annotations.obsByName[col];
case Field.var: case "var":
if (typeof col === "object") if (typeof col === "object")
throw new Error("unable to get column schema by query"); throw new Error("unable to get column schema by query");
return schema.annotations.varByName[col]; return schema.annotations.varByName[col];
case Field.emb: case "emb":
if (typeof col === "object") if (typeof col === "object")
throw new Error("unable to get column schema by query"); throw new Error("unable to get column schema by query");
return schema.layout.obsByName[col]; return schema.layout.obsByName[col];
case Field.X: case "X":
return schema.dataframe; return schema.dataframe;
default: default:
throw new Error(`unknown field name: ${field}`); throw new Error(`unknown field name: ${field}`);
} }
} }
export function _isIndex( export function _isIndex(schema, field, col) {
schema: Schema,
field: Field.obs | Field.var,
col: LabelType
): boolean {
const index = schema.annotations?.[field].index; const index = schema.annotations?.[field].index;
return !!(index && index === col); return index && index === col;
} }
export function _getColumnDimensionNames( export function _getColumnDimensionNames(schema, field, col) {
schema: Schema,
field: Field,
col: LabelType
): LabelArray | undefined {
/* /*
field/col may be an alias for multiple columns. Currently used to map ND field/col may be an alias for multiple columns. Currently used to map ND
values to 1D dataframe columns for embeddings/layout. Signified by the presence values to 1D dataframe columns for embeddings/layout. Signified by the presence
@@ -58,33 +38,30 @@ export function _getColumnDimensionNames(
if (!colSchema) { if (!colSchema) {
return undefined; return undefined;
} }
if ("dims" in colSchema) { return colSchema.dims || [col];
return colSchema.dims;
}
return [col];
} }
export function _schemaColumns(schema: Schema, field: Field): string[] { export function _schemaColumns(schema, field) {
switch (field) { switch (field) {
case Field.obs: case "obs":
return Object.keys(schema.annotations.obsByName); return Object.keys(schema.annotations.obsByName);
case Field.var: case "var":
return Object.keys(schema.annotations.varByName); return Object.keys(schema.annotations.varByName);
case Field.emb: case "emb":
return Object.keys(schema.layout.obsByName); return Object.keys(schema.layout.obsByName);
default: default:
throw new Error(`unknown field name: ${field}`); throw new Error(`unknown field name: ${field}`);
} }
} }
export function _getWritableColumns(schema: Schema, field: Field): string[] { export function _getWritableColumns(schema, field) {
if (field !== Field.obs) return []; if (field !== "obs") return [];
return schema.annotations.obs.columns return schema.annotations.obs.columns
.filter((v: AnnotationColumnSchema) => v.writable) .filter((v) => v.writable)
.map((v: AnnotationColumnSchema) => v.name); .map((v) => v.name);
} }
export function _isContinuousType(schema: ArraySchema): boolean { export function _isContinuousType(schema) {
const { type } = schema; const { type } = schema;
return !(type === "string" || type === "boolean" || type === "categorical"); return !(type === "string" || type === "boolean" || type === "categorical");
} }
@@ -4,18 +4,8 @@ instances of AnnoMatrix, implementing common UI functions.
*/ */
import { AnnoMatrixRowSubsetView, AnnoMatrixClipView } from "./views"; import { AnnoMatrixRowSubsetView, AnnoMatrixClipView } from "./views";
import AnnoMatrix from "./annoMatrix";
import {
DenseInt32Index,
IdentityInt32Index,
KeyIndex,
} from "../util/dataframe";
import { OffsetArray } from "../util/dataframe/types";
export function isubsetMask( export function isubsetMask(annoMatrix, obsMask) {
annoMatrix: AnnoMatrix,
obsMask: Uint8Array
): AnnoMatrixRowSubsetView {
/* /*
Subset annomatrix to contain the rows which have truish value in the mask. Subset annomatrix to contain the rows which have truish value in the mask.
Maks length must equal annoMatrix.nObs (row count). Maks length must equal annoMatrix.nObs (row count).
@@ -23,10 +13,7 @@ export function isubsetMask(
return isubset(annoMatrix, _maskToList(obsMask)); return isubset(annoMatrix, _maskToList(obsMask));
} }
export function isubset( export function isubset(annoMatrix, obsOffsets) {
annoMatrix: AnnoMatrix,
obsOffsets: OffsetArray
): AnnoMatrixRowSubsetView {
/* /*
Subset annomatrix to contain the positions contained in the obsOffsets array Subset annomatrix to contain the positions contained in the obsOffsets array
@@ -38,10 +25,7 @@ export function isubset(
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
} }
export function subset( export function subset(annoMatrix, obsLabels) {
annoMatrix: AnnoMatrix,
obsLabels: Int32Array
): AnnoMatrixRowSubsetView {
/* /*
subset based on labels subset based on labels
*/ */
@@ -49,21 +33,14 @@ export function subset(
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
} }
export function subsetByIndex( export function subsetByIndex(annoMatrix, obsIndex) {
annoMatrix: AnnoMatrix,
obsIndex: DenseInt32Index | IdentityInt32Index | KeyIndex
): AnnoMatrixRowSubsetView {
/* /*
subset based upon the new obs index. subset based upon the new obs index.
*/ */
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
} }
export function clip( export function clip(annoMatrix, qmin, qmax) {
annoMatrix: AnnoMatrix,
qmin: number,
qmax: number
): AnnoMatrix {
/* /*
Create a view that clips all continuous data to the [min, max] range. Create a view that clips all continuous data to the [min, max] range.
The matrix shape does not change, but the continuous values outside the The matrix shape does not change, but the continuous values outside the
@@ -76,8 +53,11 @@ export function clip(
Private utility functions below Private utility functions below
*/ */
function _maskToList(mask: Uint8Array): OffsetArray { function _maskToList(mask) {
/* convert masks to lists - method wastes space, but is fast */ /* convert masks to lists - method wastes space, but is fast */
if (!mask) {
return null;
}
const list = new Int32Array(mask.length); const list = new Int32Array(mask.length);
let elems = 0; let elems = 0;
for (let i = 0, l = mask.length; i < l; i += 1) { for (let i = 0, l = mask.length; i < l; i += 1) {
@@ -5,51 +5,25 @@ Views on the annomatrix. all API here is defined in viewCreators.js and annoMat
*/ */
import clip from "../util/clip"; import clip from "../util/clip";
import AnnoMatrix from "./annoMatrix"; import AnnoMatrix from "./annoMatrix";
import { _whereCacheCreate, WhereCache } from "./whereCache"; import { _whereCacheCreate } from "./whereCache";
import { _isContinuousType, _getColumnSchema } from "./schema"; import { _isContinuousType, _getColumnSchema } from "./schema";
import {
Dataframe,
DataframeValue,
DataframeValueArray,
LabelType,
} from "../util/dataframe";
import { Query } from "./query";
import {
AnnotationColumnSchema,
ArraySchema,
Field,
EmbeddingSchema,
} from "../common/types/schema";
import { LabelIndexBase } from "../util/dataframe/labelIndex";
type MapFn = ( class AnnoMatrixView extends AnnoMatrix {
field: Field, constructor(viewOf, rowIndex = null) {
colLabel: LabelType,
colSchema: ArraySchema,
colData: DataframeValueArray,
df: Dataframe
) => DataframeValueArray;
abstract class AnnoMatrixView extends AnnoMatrix {
constructor(viewOf: AnnoMatrix, rowIndex: LabelIndexBase | null = null) {
const nObs = rowIndex ? rowIndex.size() : viewOf.nObs; const nObs = rowIndex ? rowIndex.size() : viewOf.nObs;
super(viewOf.schema, nObs, viewOf.nVar, rowIndex || viewOf.rowIndex); super(viewOf.schema, nObs, viewOf.nVar, rowIndex || viewOf.rowIndex);
this.viewOf = viewOf; this.viewOf = viewOf;
this.isView = true; this.isView = true;
} }
addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix { addObsAnnoCategory(col, category) {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.addObsAnnoCategory(col, category); newAnnoMatrix.viewOf = this.viewOf.addObsAnnoCategory(col, category);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
return newAnnoMatrix; return newAnnoMatrix;
} }
async removeObsAnnoCategory( async removeObsAnnoCategory(col, category, unassignedCategory) {
col: LabelType,
category: string,
unassignedCategory: string
): Promise<AnnoMatrix> {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = await this.viewOf.removeObsAnnoCategory( newAnnoMatrix.viewOf = await this.viewOf.removeObsAnnoCategory(
col, col,
@@ -60,7 +34,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
dropObsColumn(col: LabelType): AnnoMatrix { dropObsColumn(col) {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.dropObsColumn(col); newAnnoMatrix.viewOf = this.viewOf.dropObsColumn(col);
newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col); newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col);
@@ -68,29 +42,21 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
addObsColumn<T extends DataframeValueArray>( addObsColumn(colSchema, Ctor, value) {
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): AnnoMatrix {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.addObsColumn(colSchema, Ctor, value); newAnnoMatrix.viewOf = this.viewOf.addObsColumn(colSchema, Ctor, value);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
return newAnnoMatrix; return newAnnoMatrix;
} }
renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix { renameObsColumn(oldCol, newCol) {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.renameObsColumn(oldCol, newCol); newAnnoMatrix.viewOf = this.viewOf.renameObsColumn(oldCol, newCol);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
return newAnnoMatrix; return newAnnoMatrix;
} }
async setObsColumnValues( async setObsColumnValues(col, rowLabels, value) {
col: LabelType,
rowLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrix> {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = await this.viewOf.setObsColumnValues( newAnnoMatrix.viewOf = await this.viewOf.setObsColumnValues(
col, col,
@@ -102,11 +68,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
async resetObsColumnValues<T extends DataframeValue>( async resetObsColumnValues(col, oldValue, newValue) {
col: LabelType,
oldValue: T,
newValue: T
): Promise<AnnoMatrix> {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = await this.viewOf.resetObsColumnValues( newAnnoMatrix.viewOf = await this.viewOf.resetObsColumnValues(
col, col,
@@ -118,7 +80,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix; return newAnnoMatrix;
} }
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix { addEmbedding(colSchema) {
const newAnnoMatrix = this._clone(); const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.addEmbedding(colSchema); newAnnoMatrix.viewOf = this.viewOf.addEmbedding(colSchema);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
@@ -127,32 +89,21 @@ abstract class AnnoMatrixView extends AnnoMatrix {
} }
class AnnoMatrixMapView extends AnnoMatrixView { class AnnoMatrixMapView extends AnnoMatrixView {
mapFn: MapFn;
/* /*
A view which knows how to transform its data. A view which knows how to transform its data.
*/ */
constructor(viewOf: AnnoMatrix, mapFn: MapFn) { constructor(viewOf, mapFn) {
super(viewOf); super(viewOf);
this.mapFn = mapFn; this.mapFn = mapFn;
} }
async _doLoad( async _doLoad(field, query) {
field: Field,
query: Query
): Promise<[WhereCache | null, Dataframe]> {
const df = await this.viewOf._fetch(field, query); const df = await this.viewOf._fetch(field, query);
const dfMapped = df.mapColumns( const dfMapped = df.mapColumns((colData, colIdx) => {
(colData: DataframeValueArray, colIdx: number) => { const colLabel = df.colIndex.getLabel(colIdx);
const colLabel = df.colIndex.getLabel(colIdx); const colSchema = _getColumnSchema(this.schema, field, colLabel);
// @ts-expect-error ts-migrate --- TODO revisit: return this.mapFn(field, colLabel, colSchema, colData, df);
// `colLabel`: Argument of type 'LabelType | undefined' is not assignable to parameter of type 'LabelType'. });
const colSchema = _getColumnSchema(this.schema, field, colLabel);
// @ts-expect-error ts-migrate --- TODO revisit:
// `colLabel`: Argument of type 'LabelType | undefined' is not assignable to parameter of type 'LabelType'.
return this.mapFn(field, colLabel, colSchema, colData, df);
}
);
const whereCacheUpdate = _whereCacheCreate( const whereCacheUpdate = _whereCacheCreate(
field, field,
query, query,
@@ -163,23 +114,12 @@ class AnnoMatrixMapView extends AnnoMatrixView {
} }
export class AnnoMatrixClipView extends AnnoMatrixMapView { export class AnnoMatrixClipView extends AnnoMatrixMapView {
clipRange: [number, number];
isClipped: boolean;
/* /*
A view which is a clipped transformation of its parent A view which is a clipped transformation of its parent
*/ */
constructor(viewOf: AnnoMatrix, qmin: number, qmax: number) { constructor(viewOf, qmin, qmax) {
super( super(viewOf, (field, colLabel, colSchema, colData, df) =>
viewOf, _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax)
(
field: Field,
colLabel: LabelType,
colSchema: ArraySchema,
colData: DataframeValueArray,
df: Dataframe
) => _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax)
); );
this.isClipped = true; this.isClipped = true;
this.clipRange = [qmin, qmax]; this.clipRange = [qmin, qmax];
@@ -189,21 +129,18 @@ export class AnnoMatrixClipView extends AnnoMatrixMapView {
export class AnnoMatrixRowSubsetView extends AnnoMatrixView { export class AnnoMatrixRowSubsetView extends AnnoMatrixView {
/* /*
A view which is a subset of total rows. A view which is a subset of total rows.
*/ */
constructor(viewOf: AnnoMatrix, rowIndex: LabelIndexBase) { constructor(viewOf, rowIndex) {
super(viewOf, rowIndex); super(viewOf, rowIndex);
Object.seal(this); Object.seal(this);
} }
async _doLoad( async _doLoad(field, query) {
field: Field,
query: Query
): Promise<[WhereCache | null, Dataframe]> {
const df = await this.viewOf._fetch(field, query); const df = await this.viewOf._fetch(field, query);
// don't try to row-subset the var dimension. // don't try to row-subset the var dimension.
if (field === Field.var) { if (field === "var") {
return [null, df]; return [null, df];
} }
@@ -221,23 +158,15 @@ export class AnnoMatrixRowSubsetView extends AnnoMatrixView {
Utility functions below Utility functions below
*/ */
function _clipAnnoMatrix( function _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax) {
field: Field,
colLabel: LabelType,
colSchema: ArraySchema,
colData: DataframeValueArray,
df: Dataframe,
qmin: number,
qmax: number
): DataframeValueArray {
/* only clip obs and var scalar columns */ /* only clip obs and var scalar columns */
if (field !== Field.obs && field !== Field.X) return colData; if (field !== "obs" && field !== "X") return colData;
if (!_isContinuousType(colSchema)) return colData; if (!_isContinuousType(colSchema)) return colData;
if (qmin < 0) qmin = 0; if (qmin < 0) qmin = 0;
if (qmax > 1) qmax = 1; if (qmax > 1) qmax = 1;
if (qmin === 0 && qmax === 1) return colData; if (qmin === 0 && qmax === 1) return colData;
const quantiles = df.col(colLabel).summarizeContinuous().percentiles; const quantiles = df.col(colLabel).summarize().percentiles;
const lower = quantiles[100 * qmin]; const lower = quantiles[100 * qmin];
const upper = quantiles[100 * qmax]; const upper = quantiles[100 * qmax];
const clippedData = clip(colData.slice(), lower, upper, Number.NaN); const clippedData = clip(colData.slice(), lower, upper, Number.NaN);
@@ -2,7 +2,7 @@
Private support functions. Private support functions.
This implements a query resolver cache, mapping a query onto the column labels This implements a query resolver cache, mapping a query onto the column labels
resolved by that query. These labels are then used to manage the actual data cache, resolved by that query. These labels are then used to manage the acutal data cache,
which stores data by the resolved label. which stores data by the resolved label.
There are three query forms: There are three query forms:
@@ -49,33 +49,9 @@ creates a cache entry of:
} }
*/ */
import { _getColumnDimensionNames } from "./schema"; import { _getColumnDimensionNames } from "./schema";
import { _hashStringValues, Query } from "./query"; import { _hashStringValues } from "./query";
import { Field, Schema } from "../common/types/schema";
import { LabelArray } from "../util/dataframe/types";
export interface WhereCache { export function _whereCacheGet(whereCache, schema, field, query) {
summarize?: {
[key: string]: {
[key: string]: WhereCacheTerms;
};
};
where?: {
[key: string]: WhereCacheTerms;
};
}
export type WhereCacheColumnLabels = LabelArray;
interface WhereCacheTerms {
[key: string]: Map<string, Map<string, WhereCacheColumnLabels>>;
}
export function _whereCacheGet(
whereCache: WhereCache,
schema: Schema,
field: Field,
query: Query
): WhereCacheColumnLabels | [undefined] {
/* /*
query will either be an where query (object) or a column name (string). query will either be an where query (object) or a column name (string).
@@ -83,7 +59,7 @@ export function _whereCacheGet(
*/ */
if (typeof query === "object") { if (typeof query === "object") {
if ("where" in query) { if (query.where) {
const { const {
field: queryField, field: queryField,
column: queryColumn, column: queryColumn,
@@ -92,7 +68,7 @@ export function _whereCacheGet(
const columnMap = whereCache?.where?.[field]?.[queryField]; const columnMap = whereCache?.where?.[field]?.[queryField];
return columnMap?.get(queryColumn)?.get(queryValue) ?? [undefined]; return columnMap?.get(queryColumn)?.get(queryValue) ?? [undefined];
} }
if ("summarize" in query) { if (query.summarize) {
const { const {
method, method,
field: queryField, field: queryField,
@@ -109,17 +85,13 @@ export function _whereCacheGet(
return _getColumnDimensionNames(schema, field, query) ?? [undefined]; return _getColumnDimensionNames(schema, field, query) ?? [undefined];
} }
export function _whereCacheCreate( export function _whereCacheCreate(field, query, columnLabels) {
field: Field,
query: Query,
columnLabels: LabelArray
): WhereCache | null {
/* /*
Create a new whereCache Create a new whereCache
*/ */
if (typeof query !== "object") return null; if (typeof query !== "object") return null;
if ("where" in query) { if (query.where) {
const { const {
field: queryField, field: queryField,
column: queryColumn, column: queryColumn,
@@ -135,7 +107,7 @@ export function _whereCacheCreate(
}, },
}; };
} }
if ("summarize" in query) { if (query.summarize) {
const { const {
method, method,
field: queryField, field: queryField,
@@ -159,25 +131,20 @@ export function _whereCacheCreate(
return {}; return {};
} }
function __mergeQueries(dst: WhereCacheTerms, src: WhereCacheTerms) { function __mergeQueries(dst, src) {
for (const [queryField, columnMap] of Object.entries(src)) { for (const [queryField, columnMap] of Object.entries(src)) {
dst[queryField] = dst[queryField] || new Map(); dst[queryField] = dst[queryField] || new Map();
for (const [queryColumn, valueMap] of columnMap) { for (const [queryColumn, valueMap] of columnMap) {
if (!dst[queryField].has(queryColumn)) if (!dst[queryField].has(queryColumn))
dst[queryField].set(queryColumn, new Map()); dst[queryField].set(queryColumn, new Map());
for (const [queryValue, columnLabels] of valueMap) { for (const [queryValue, columnLabels] of valueMap) {
// @ts-expect-error ts-migrate --- TODO revisit:
// `dst[queryField].get(queryColumn)` Object is possibly 'undefined'.
dst[queryField].get(queryColumn).set(queryValue, columnLabels); dst[queryField].get(queryColumn).set(queryValue, columnLabels);
} }
} }
} }
} }
function __whereCacheMerge( function __whereCacheMerge(dst, src) {
dst: WhereCache,
src: WhereCache | null
): WhereCache {
/* /*
merge src into dst (modifies dst) merge src into dst (modifies dst)
*/ */
@@ -204,6 +171,6 @@ function __whereCacheMerge(
return dst; return dst;
} }
export function _whereCacheMerge(...caches: (WhereCache | null)[]): WhereCache { export function _whereCacheMerge(...caches) {
return caches.reduce(__whereCacheMerge, {} as WhereCache); return caches.reduce(__whereCacheMerge, {});
} }
-102
View File
@@ -1,102 +0,0 @@
/**
* Utility type and interface definitions.
*/
/**
* TypedArrays that can be assigned to a number.
*/
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array;
export type UnsignedTypedArray = Uint8Array | Uint16Array | Uint32Array;
export type FloatTypedArray = Float32Array | Float64Array;
export type TypedArrayConstructor =
| Int8ArrayConstructor
| Uint8ArrayConstructor
| Int16ArrayConstructor
| Uint16ArrayConstructor
| Int32ArrayConstructor
| Uint32ArrayConstructor
| Float32ArrayConstructor
| Float64ArrayConstructor;
export type AnyArray = Array<unknown> | TypedArray;
export interface GenericArrayConstructor<T extends AnyArray> {
new (
...args: ConstructorParameters<
typeof Int8Array &
typeof Uint8Array &
typeof Int16Array &
typeof Uint16Array &
typeof Int32Array &
typeof Uint32Array &
typeof Float32Array &
typeof Float64Array &
typeof Array
>
): T;
}
export type NumberArray = Array<number> | TypedArray;
export type Int8 = Int8Array[0];
export type Uint8 = Uint8Array[0];
export type Int16 = Int16Array[0];
export type Uint16 = Uint16Array[0];
export type Int32 = Int32Array[0];
export type Uint32 = Uint32Array[0];
export type Float32 = Float32Array[0];
export type Float64 = Float64Array[0];
/**
* Test if the parameter is a TypedArray.
* @param tbd - value to be tested
* @returns true if `tbd` is a TypedArray, false if not.
*/
export function isTypedArray(tbd: unknown): tbd is TypedArray {
return (
ArrayBuffer.isView(tbd) &&
Object.prototype.toString.call(tbd) !== "[object DataView]"
);
}
/**
* Test if the paramter is a float TypedArray
* @param tbd - value to be tested
* @returns - true if `tbd` is a float typed array.
*/
export function isFloatTypedArray(tbd: unknown): tbd is FloatTypedArray {
return tbd instanceof Float32Array || tbd instanceof Float64Array;
}
/**
* Test if the paramter is a float TypedArray
* @param tbd - value to be tested
* @returns - true if `tbd` is a float typed array.
*/
export function isUnsignedTypedArray(tbd: unknown): tbd is UnsignedTypedArray {
return (
tbd instanceof Uint8Array ||
tbd instanceof Uint16Array ||
tbd instanceof Uint32Array
);
}
/**
* Test if the parameter is a TypedArray or Array
* @param tbd - value to be tested
* @returns - true if `tbd` is a TypedArray or Array
*/
export function isAnyArray(tbd: unknown): tbd is AnyArray {
return Array.isArray(tbd) || isTypedArray(tbd);
}
-8
View File
@@ -1,8 +0,0 @@
// If a globally shared type or interface doesn't have a clear owner, put it here
/**
* Flags informing garbage collection-related logic.
*/
export interface GCHints {
isHot: boolean;
}
-76
View File
@@ -1,76 +0,0 @@
export type Category = number | string | boolean;
export interface AnnotationColumnSchema {
categories?: Category[];
name: string;
type: "string" | "float32" | "int32" | "categorical" | "boolean";
writable: boolean;
}
export interface XMatrixSchema {
nObs: number;
nVar: number;
// TODO(thuang): Not sure what other types are available
type: "float32";
}
export interface EmbeddingSchema {
dims: string[];
name: string;
// TODO(thuang): Not sure what other types are available
type: "float32";
}
interface RawLayoutSchema {
obs: EmbeddingSchema[];
var?: EmbeddingSchema[];
}
interface RawAnnotationsSchema {
obs: {
columns: AnnotationColumnSchema[];
index: string;
};
var: {
columns: AnnotationColumnSchema[];
index: string;
};
}
export interface RawSchema {
annotations: RawAnnotationsSchema;
dataframe: XMatrixSchema;
layout: RawLayoutSchema;
}
interface AnnotationsSchema extends RawAnnotationsSchema {
obsByName: { [name: string]: AnnotationColumnSchema };
varByName: { [name: string]: AnnotationColumnSchema };
}
interface LayoutSchema extends RawLayoutSchema {
obsByName: { [name: string]: EmbeddingSchema };
varByName: { [name: string]: EmbeddingSchema };
}
export interface Schema extends RawSchema {
annotations: AnnotationsSchema;
layout: LayoutSchema;
}
/**
* Sub-schema objects describing the schema for a primitive Array or Matrix in one of the fields.
*/
export type ArraySchema =
| AnnotationColumnSchema
| EmbeddingSchema
| XMatrixSchema;
/**
* Set of data / metadata objects that must be specified in a CXG.
*/
export enum Field {
"obs" = "obs",
"var" = "var",
"emb" = "emb",
"X" = "X",
}
+94
View File
@@ -0,0 +1,94 @@
import React from "react";
import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core";
class AnnoDialog extends React.PureComponent {
constructor(props) {
super(props);
this.state = {};
}
render() {
const {
isActive,
text,
title,
instruction,
cancelTooltipContent,
errorMessage,
validationError,
annoSelect,
annoInput,
secondaryInstructions,
secondaryInput,
handleCancel,
handleSubmit,
primaryButtonText,
secondaryButtonText,
handleSecondaryButtonSubmit,
primaryButtonProps,
} = this.props;
return (
<Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}>
<form
onSubmit={(e) => {
e.preventDefault();
}}
>
<div className={Classes.DIALOG_BODY}>
<div style={{ marginBottom: 20 }}>
<p>{instruction}</p>
{annoInput || null}
<p
style={{
marginTop: 7,
visibility: validationError ? "visible" : "hidden",
color: Colors.ORANGE3,
}}
>
{errorMessage}
</p>
{/* we might rename, secondary button and secondary input are not related */}
{secondaryInstructions && (
<p style={{ marginTop: secondaryInstructions ? 20 : 0 }}>
{secondaryInstructions}
</p>
)}
{secondaryInput || null}
</div>
{annoSelect || null}
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Tooltip content={cancelTooltipContent}>
<Button onClick={handleCancel}>Cancel</Button>
</Tooltip>
{/* we might rename, secondary button and secondary input are not related */}
{handleSecondaryButtonSubmit && secondaryButtonText ? (
<Button
onClick={handleSecondaryButtonSubmit}
disabled={!text || validationError}
intent="none"
type="button"
>
{secondaryButtonText}
</Button>
) : null}
<Button
{...primaryButtonProps} // eslint-disable-line react/jsx-props-no-spreading -- Spreading props allows for modularity
onClick={handleSubmit}
disabled={!text || validationError}
intent="primary"
type="submit"
>
{primaryButtonText}
</Button>
</div>
</div>
</form>
</Dialog>
);
}
}
export default AnnoDialog;
-117
View File
@@ -1,117 +0,0 @@
import React from "react";
import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
type State = any;
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
class AnnoDialog extends React.PureComponent<{}, State> {
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
constructor(props: {}) {
super(props);
this.state = {};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
render() {
const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isActive' does not exist on type 'Readon... Remove this comment to see the full error message
isActive,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Readonly<{... Remove this comment to see the full error message
text,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'title' does not exist on type 'Readonly<... Remove this comment to see the full error message
title,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'instruction' does not exist on type 'Rea... Remove this comment to see the full error message
instruction,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'cancelTooltipContent' does not exist on ... Remove this comment to see the full error message
cancelTooltipContent,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'errorMessage' does not exist on type 'Re... Remove this comment to see the full error message
errorMessage,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'validationError' does not exist on type ... Remove this comment to see the full error message
validationError,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoSelect' does not exist on type 'Read... Remove this comment to see the full error message
annoSelect,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoInput' does not exist on type 'Reado... Remove this comment to see the full error message
annoInput,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'secondaryInstructions' does not exist on... Remove this comment to see the full error message
secondaryInstructions,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'secondaryInput' does not exist on type '... Remove this comment to see the full error message
secondaryInput,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleCancel' does not exist on type 'Re... Remove this comment to see the full error message
handleCancel,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleSubmit' does not exist on type 'Re... Remove this comment to see the full error message
handleSubmit,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'primaryButtonText' does not exist on typ... Remove this comment to see the full error message
primaryButtonText,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'secondaryButtonText' does not exist on t... Remove this comment to see the full error message
secondaryButtonText,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'handleSecondaryButtonSubmit' does not ex... Remove this comment to see the full error message
handleSecondaryButtonSubmit,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'primaryButtonProps' does not exist on ty... Remove this comment to see the full error message
primaryButtonProps,
} = this.props;
return (
<Dialog icon="tag" title={title} isOpen={isActive} onClose={handleCancel}>
<form
onSubmit={(e) => {
e.preventDefault();
}}
>
<div className={Classes.DIALOG_BODY}>
<div style={{ marginBottom: 20 }}>
<p>{instruction}</p>
{annoInput || null}
<p
style={{
marginTop: 7,
visibility: validationError ? "visible" : "hidden",
color: Colors.ORANGE3,
}}
>
{errorMessage}
</p>
{/* we might rename, secondary button and secondary input are not related */}
{secondaryInstructions && (
<p style={{ marginTop: secondaryInstructions ? 20 : 0 }}>
{secondaryInstructions}
</p>
)}
{secondaryInput || null}
</div>
{annoSelect || null}
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Tooltip content={cancelTooltipContent}>
<Button onClick={handleCancel}>Cancel</Button>
</Tooltip>
{/* we might rename, secondary button and secondary input are not related */}
{handleSecondaryButtonSubmit && secondaryButtonText ? (
<Button
onClick={handleSecondaryButtonSubmit}
disabled={!text || validationError}
intent="none"
type="button"
>
{secondaryButtonText}
</Button>
) : null}
<Button
{...primaryButtonProps} // eslint-disable-line react/jsx-props-no-spreading -- Spreading props allows for modularity
onClick={handleSubmit}
disabled={!text || validationError}
intent="primary"
type="submit"
>
{primaryButtonText}
</Button>
</div>
</div>
</form>
</Dialog>
);
}
}
export default AnnoDialog;
@@ -15,38 +15,30 @@ import TermsOfServicePrompt from "./termsPrompt";
import actions from "../actions"; import actions from "../actions";
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
@connect((state) => ({ @connect((state) => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. loading: state.controls.loading,
loading: (state as any).controls.loading, error: state.controls.error,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. graphRenderCounter: state.controls.graphRenderCounter,
error: (state as any).controls.error,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
graphRenderCounter: (state as any).controls.graphRenderCounter,
})) }))
class App extends React.Component { class App extends React.Component {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
componentDidMount() { componentDidMount() {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
const { dispatch } = this.props; const { dispatch } = this.props;
/* listen for url changes, fire one when we start the app up */ /* listen for url changes, fire one when we start the app up */
window.addEventListener("popstate", this._onURLChanged); window.addEventListener("popstate", this._onURLChanged);
this._onURLChanged(); this._onURLChanged();
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
dispatch(actions.doInitialDataLoad(window.location.search)); dispatch(actions.doInitialDataLoad(window.location.search));
this.forceUpdate(); this.forceUpdate();
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_onURLChanged() { _onURLChanged() {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch({ type: "url changed", url: document.location.href }); dispatch({ type: "url changed", url: document.location.href });
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
render() { render() {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'loading' does not exist on type 'Readonl... Remove this comment to see the full error message
const { loading, error, graphRenderCounter } = this.props; const { loading, error, graphRenderCounter } = this.props;
return ( return (
<Container> <Container>
@@ -78,16 +70,13 @@ class App extends React.Component {
{loading || error ? null : ( {loading || error ? null : (
<Layout> <Layout>
<LeftSideBar /> <LeftSideBar />
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */} {(viewportRef) => (
{(viewportRef: any) => (
<> <>
<MenuBar /> <MenuBar />
<Embedding /> <Embedding />
<Autosave /> <Autosave />
<TermsOfServicePrompt /> <TermsOfServicePrompt />
{/* @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. */}
<Legend viewportRef={viewportRef} /> <Legend viewportRef={viewportRef} />
{/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: any; viewportRef: any; }' is not assi... Remove this comment to see the full error message */}
<Graph key={graphRenderCounter} viewportRef={viewportRef} /> <Graph key={graphRenderCounter} viewportRef={viewportRef} />
</> </>
)} )}
@@ -11,78 +11,60 @@ import {
Tooltip, Tooltip,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
type State = any;
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
@connect((state) => ({ @connect((state) => ({
idhash: idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. annotations: state.annotations,
(state as any).config?.parameters?.["annotations-user-data-idhash"] ?? null, auth: state.config?.authentication,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. userInfo: state.userInfo,
annotations: (state as any).annotations, writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
auth: (state as any).config?.authentication,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
userInfo: (state as any).userInfo,
writableCategoriesEnabled:
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(state as any).config?.parameters?.annotations ?? false,
writableGenesetsEnabled: !( writableGenesetsEnabled: !(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. state.config?.parameters?.annotations_genesets_readonly ?? true
((state as any).config?.parameters?.annotations_genesets_readonly ?? true)
), ),
})) }))
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS. class FilenameDialog extends React.Component {
class FilenameDialog extends React.Component<{}, State> { constructor(props) {
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
constructor(props: {}) {
super(props); super(props);
this.state = { this.state = {
filenameText: "", filenameText: "",
}; };
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
dismissFilenameDialog = () => {}; dismissFilenameDialog = () => {};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
handleCreateFilename = () => { handleCreateFilename = () => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
const { dispatch } = this.props; const { dispatch } = this.props;
const { filenameText } = this.state; const { filenameText } = this.state;
dispatch({ dispatch({
type: "set annotations collection name", type: "set annotations collection name",
data: filenameText, data: filenameText,
}); });
}; };
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
filenameError = () => { filenameError = () => {
const legalNames = /^\w+$/; const legalNames = /^\w+$/;
const { filenameText } = this.state; const { filenameText } = this.state;
let err = false; let err = false;
if (filenameText === "") { if (filenameText === "") {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'.
err = "empty_string"; err = "empty_string";
} else if (!legalNames.test(filenameText)) { } else if (!legalNames.test(filenameText)) {
/* /*
IMPORTANT: this test must ultimately match the test applied by the IMPORTANT: this test must ultimately match the test applied by the
backend, which is designed to ensure a safe file name can be created backend, which is designed to ensure a safe file name can be created
from the data collection name. If you change this, you will also need from the data collection name. If you change this, you will also need
to change the validation code in the backend, or it will have no effect. to change the validation code in the backend, or it will have no effect.
*/ */
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'.
err = "characters"; err = "characters";
} }
return err; return err;
}; };
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
filenameErrorMessage = () => { filenameErrorMessage = () => {
const err = this.filenameError(); const err = this.filenameError();
let markup = null; let markup = null;
// @ts-expect-error ts-migrate(2367) FIXME: This condition will always return 'false' since th... Remove this comment to see the full error message
if (err === "empty_string") { if (err === "empty_string") {
markup = ( markup = (
<span <span
@@ -96,7 +78,6 @@ class FilenameDialog extends React.Component<{}, State> {
Name cannot be blank Name cannot be blank
</span> </span>
); );
// @ts-expect-error ts-migrate(2367) FIXME: This condition will always return 'false' since th... Remove this comment to see the full error message
} else if (err === "characters") { } else if (err === "characters") {
markup = ( markup = (
<span <span
@@ -114,21 +95,16 @@ class FilenameDialog extends React.Component<{}, State> {
return markup; return markup;
}; };
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
render() { render() {
const { const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message
writableCategoriesEnabled, writableCategoriesEnabled,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableGenesetsEnabled' does not exist ... Remove this comment to see the full error message
writableGenesetsEnabled, writableGenesetsEnabled,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annotations' does not exist on type 'Rea... Remove this comment to see the full error message
annotations, annotations,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'idhash' does not exist on type 'Readonly... Remove this comment to see the full error message
idhash, idhash,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'userInfo' does not exist on type 'Readon... Remove this comment to see the full error message
userInfo, userInfo,
} = this.props; } = this.props;
const { filenameText } = this.state; const { filenameText } = this.state;
return (writableCategoriesEnabled || writableGenesetsEnabled) && return (writableCategoriesEnabled || writableGenesetsEnabled) &&
annotations.promptForFilename && annotations.promptForFilename &&
!annotations.dataCollectionNameIsReadOnly && !annotations.dataCollectionNameIsReadOnly &&
@@ -152,7 +128,6 @@ class FilenameDialog extends React.Component<{}, State> {
<InputGroup <InputGroup
autoFocus autoFocus
value={filenameText} value={filenameText}
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
intent={this.filenameError(filenameText) ? "warning" : "none"} intent={this.filenameError(filenameText) ? "warning" : "none"}
onChange={(e) => onChange={(e) =>
this.setState({ filenameText: e.target.value }) this.setState({ filenameText: e.target.value })
@@ -163,14 +138,12 @@ class FilenameDialog extends React.Component<{}, State> {
<p <p
style={{ style={{
marginTop: 7, marginTop: 7,
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
visibility: this.filenameError(filenameText) visibility: this.filenameError(filenameText)
? "visible" ? "visible"
: "hidden", : "hidden",
color: Colors.ORANGE3, color: Colors.ORANGE3,
}} }}
> >
{/* @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1. */}
{this.filenameErrorMessage(filenameText)} {this.filenameErrorMessage(filenameText)}
</p> </p>
</div> </div>
@@ -198,7 +171,6 @@ class FilenameDialog extends React.Component<{}, State> {
<Button onClick={this.dismissFilenameDialog}>Cancel</Button> <Button onClick={this.dismissFilenameDialog}>Cancel</Button>
</Tooltip> </Tooltip>
<Button <Button
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
disabled={!filenameText || this.filenameError(filenameText)} disabled={!filenameText || this.filenameError(filenameText)}
onClick={this.handleCreateFilename} onClick={this.handleCreateFilename}
intent="primary" intent="primary"
+122
View File
@@ -0,0 +1,122 @@
import React from "react";
import { connect } from "react-redux";
import actions from "../../actions";
import FilenameDialog from "./filenameDialog";
@connect((state) => ({
annotations: state.annotations,
obsAnnotationSaveInProgress:
state.autosave?.obsAnnotationSaveInProgress ?? false,
genesetSaveInProgress: state.autosave?.genesetSaveInProgress ?? false,
error: state.autosave?.error,
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
writableGenesetsEnabled: !(
state.config?.parameters?.annotations_genesets_readonly ?? true
),
annoMatrix: state.annoMatrix,
genesets: state.genesets,
lastSavedAnnoMatrix: state.autosave?.lastSavedAnnoMatrix,
lastSavedGenesets: state.autosave?.lastSavedGenesets,
}))
class Autosave extends React.Component {
constructor(props) {
super(props);
this.state = {
timer: null,
};
}
componentDidMount() {
const { writableCategoriesEnabled, writableGenesetsEnabled } = this.props;
let { timer } = this.state;
if (timer) clearInterval(timer);
if (writableCategoriesEnabled || writableGenesetsEnabled) {
timer = setInterval(this.tick, 2500);
} else {
timer = null;
}
this.setState({ timer });
}
componentWillUnmount() {
const { timer } = this.state;
if (timer) this.clearInterval(timer);
}
tick = () => {
const { dispatch, obsAnnotationSaveInProgress, genesetSaveInProgress } =
this.props;
if (!obsAnnotationSaveInProgress && this.needToSaveObsAnnotations()) {
dispatch(actions.saveObsAnnotationsAction());
}
if (!genesetSaveInProgress && this.needToSaveGenesets()) {
dispatch(actions.saveGenesetsAction());
}
};
needToSaveObsAnnotations = () => {
/* return true if we need to save obs cell labels, false if we don't */
const { annoMatrix, lastSavedAnnoMatrix } = this.props;
return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix);
};
needToSaveGenesets = () => {
/* return true if we need to save gene ses, false if we do not */
const { genesets, lastSavedGenesets } = this.props;
return genesets.initialized && genesets.genesets !== lastSavedGenesets;
};
needToSave() {
return this.needToSaveGenesets() || this.needToSaveObsAnnotations();
}
saveInProgress() {
const { obsAnnotationSaveInProgress, genesetSaveInProgress } = this.props;
return obsAnnotationSaveInProgress || genesetSaveInProgress;
}
statusMessage() {
const { error } = this.props;
if (error) {
return `Autosave error: ${error}`;
}
return this.needToSave() ? "Unsaved" : "All saved";
}
render() {
const {
writableCategoriesEnabled,
writableGenesetsEnabled,
lastSavedAnnoMatrix,
} = this.props;
const initialDataLoadComplete = lastSavedAnnoMatrix;
if (!writableCategoriesEnabled && !writableGenesetsEnabled) return null;
return (
<div
id="autosave"
data-testclass={
!initialDataLoadComplete
? "autosave-init"
: this.saveInProgress() || this.needToSave()
? "autosave-incomplete"
: "autosave-complete"
}
style={{
position: "absolute",
display: "inherit",
right: 8,
bottom: 8,
zIndex: 1,
}}
>
{this.statusMessage()}
<FilenameDialog />
</div>
);
}
}
export default Autosave;
-160
View File
@@ -1,160 +0,0 @@
import React from "react";
import { connect } from "react-redux";
import actions from "../../actions";
import FilenameDialog from "./filenameDialog";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
type State = any;
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
@connect((state) => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
annotations: (state as any).annotations,
obsAnnotationSaveInProgress:
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(state as any).autosave?.obsAnnotationSaveInProgress ?? false,
genesetSaveInProgress:
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(state as any).autosave?.genesetSaveInProgress ?? false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
error: (state as any).autosave?.error,
writableCategoriesEnabled:
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(state as any).config?.parameters?.annotations ?? false,
writableGenesetsEnabled: !(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
((state as any).config?.parameters?.annotations_genesets_readonly ?? true)
),
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
annoMatrix: (state as any).annoMatrix,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
genesets: (state as any).genesets,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
lastSavedAnnoMatrix: (state as any).autosave?.lastSavedAnnoMatrix,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
lastSavedGenesets: (state as any).autosave?.lastSavedGenesets,
}))
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
class Autosave extends React.Component<{}, State> {
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
constructor(props: {}) {
super(props);
this.state = {
timer: null,
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
componentDidMount() {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message
const { writableCategoriesEnabled, writableGenesetsEnabled } = this.props;
let { timer } = this.state;
if (timer) clearInterval(timer);
if (writableCategoriesEnabled || writableGenesetsEnabled) {
timer = setInterval(this.tick, 2500);
} else {
timer = null;
}
this.setState({ timer });
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
componentWillUnmount() {
const { timer } = this.state;
if (timer) clearInterval(timer);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
tick = () => {
const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
dispatch,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'obsAnnotationSaveInProgress' does not ex... Remove this comment to see the full error message
obsAnnotationSaveInProgress,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesetSaveInProgress' does not exist on... Remove this comment to see the full error message
genesetSaveInProgress,
} = this.props;
if (!obsAnnotationSaveInProgress && this.needToSaveObsAnnotations()) {
dispatch(actions.saveObsAnnotationsAction());
}
if (!genesetSaveInProgress && this.needToSaveGenesets()) {
dispatch(actions.saveGenesetsAction());
}
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
needToSaveObsAnnotations = () => {
/* return true if we need to save obs cell labels, false if we don't */
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
const { annoMatrix, lastSavedAnnoMatrix } = this.props;
return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix);
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
needToSaveGenesets = () => {
/* return true if we need to save gene ses, false if we do not */
// @ts-expect-error ts-migrate(2339) FIXME: Property 'genesets' does not exist on type 'Readon... Remove this comment to see the full error message
const { genesets, lastSavedGenesets } = this.props;
return genesets.initialized && genesets.genesets !== lastSavedGenesets;
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
needToSave() {
return this.needToSaveGenesets() || this.needToSaveObsAnnotations();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
saveInProgress() {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'obsAnnotationSaveInProgress' does not ex... Remove this comment to see the full error message
const { obsAnnotationSaveInProgress, genesetSaveInProgress } = this.props;
return obsAnnotationSaveInProgress || genesetSaveInProgress;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
statusMessage() {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'error' does not exist on type 'Readonly<... Remove this comment to see the full error message
const { error } = this.props;
if (error) {
return `Autosave error: ${error}`;
}
return this.needToSave() ? "Unsaved" : "All saved";
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
render() {
const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message
writableCategoriesEnabled,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'writableGenesetsEnabled' does not exist ... Remove this comment to see the full error message
writableGenesetsEnabled,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'lastSavedAnnoMatrix' does not exist on t... Remove this comment to see the full error message
lastSavedAnnoMatrix,
} = this.props;
const initialDataLoadComplete = lastSavedAnnoMatrix;
if (!writableCategoriesEnabled && !writableGenesetsEnabled) return null;
return (
<div
id="autosave"
data-testclass={
!initialDataLoadComplete
? "autosave-init"
: this.saveInProgress() || this.needToSave()
? "autosave-incomplete"
: "autosave-complete"
}
style={{
position: "absolute",
display: "inherit",
right: 8,
bottom: 8,
zIndex: 1,
}}
>
{this.statusMessage()}
<FilenameDialog />
</div>
);
}
}
export default Autosave;
@@ -0,0 +1,15 @@
import React from "react";
import * as globals from "../../globals";
const ErrorLoading = ({ displayName, zebra }) => (
<div
style={{
backgroundColor: zebra ? globals.lightestGrey : "white",
fontStyle: "italic",
}}
>
<span>{`Failure loading ${displayName}`}</span>
</div>
);
export default ErrorLoading;
@@ -1,16 +0,0 @@
import React from "react";
import * as globals from "../../globals";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
const ErrorLoading = ({ displayName, zebra }: any) => (
<div
style={{
backgroundColor: zebra ? globals.lightestGrey : "white",
fontStyle: "italic",
}}
>
<span>{`Failure loading ${displayName}`}</span>
</div>
);
export default ErrorLoading;
@@ -0,0 +1,63 @@
import React from "react";
const HistogramFooter = React.memo(
({
displayName,
hideRanges,
rangeMin,
rangeMax,
rangeColorMin,
rangeColorMax,
isObs,
isGeneSetSummary,
}) =>
/*
Footer of each histogram. Will render range and title.
Required props:
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
* hideRanges - true/false, enables/disable rendering of ranges
* range - length two array, [min, max], containing the range values to display
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
*/
(
<div>
<div
style={{
display: "flex",
justifyContent: hideRanges ? "center" : "space-between",
}}
>
<span
style={{
color: rangeColorMin,
display: hideRanges ? "none" : "block",
}}
>
min {rangeMin.toPrecision(4)}
</span>
<span
data-testclass="brushable-histogram-field-name"
style={{ fontStyle: "italic" }}
>
{isObs && displayName}
{isGeneSetSummary && "gene set mean expression"}
</span>
<div style={{ display: hideRanges ? "block" : "none" }}>
: {rangeMin}
</div>
<span
style={{
color: rangeColorMax,
display: hideRanges ? "none" : "block",
}}
>
max {rangeMax.toPrecision(4)}
</span>
</div>
</div>
)
);
export default HistogramFooter;
@@ -1,69 +0,0 @@
import React from "react";
const HistogramFooter = React.memo(
({
// @ts-expect-error ts-migrate(2339) FIXME: Property 'displayName' does not exist on type '{ c... Remove this comment to see the full error message
displayName,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'hideRanges' does not exist on type '{ ch... Remove this comment to see the full error message
hideRanges,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeMin' does not exist on type '{ chil... Remove this comment to see the full error message
rangeMin,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeMax' does not exist on type '{ chil... Remove this comment to see the full error message
rangeMax,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeColorMin' does not exist on type '{... Remove this comment to see the full error message
rangeColorMin,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'rangeColorMax' does not exist on type '{... Remove this comment to see the full error message
rangeColorMax,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{ childre... Remove this comment to see the full error message
isObs,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
isGeneSetSummary,
}) => (
/*
Footer of each histogram. Will render range and title.
Required props:
* displayName - the displayName, aka "n_genes", "FOXP2", etc.
* hideRanges - true/false, enables/disable rendering of ranges
* range - length two array, [min, max], containing the range values to display
* rangeColor - length two array, [mincolor, maxcolor], each a CSS color
*/
<div>
<div
style={{
display: "flex",
justifyContent: hideRanges ? "center" : "space-between",
}}
>
<span
style={{
color: rangeColorMin,
display: hideRanges ? "none" : "block",
}}
>
min {rangeMin.toPrecision(4)}
</span>
<span
data-testclass="brushable-histogram-field-name"
style={{ fontStyle: "italic" }}
>
{isObs && displayName}
{isGeneSetSummary && "gene set mean expression"}
</span>
<div style={{ display: hideRanges ? "block" : "none" }}>
: {rangeMin}
</div>
<span
style={{
color: rangeColorMax,
display: hideRanges ? "none" : "block",
}}
>
max {rangeMax.toPrecision(4)}
</span>
</div>
</div>
)
);
export default HistogramFooter;
@@ -5,23 +5,14 @@ import * as globals from "../../globals";
const HistogramHeader = React.memo( const HistogramHeader = React.memo(
({ ({
// @ts-expect-error ts-migrate(2339) FIXME: Property 'fieldId' does not exist on type '{ child... Remove this comment to see the full error message
fieldId, fieldId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorBy' does not exist on type '{ chi... Remove this comment to see the full error message
isColorBy, isColorBy,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onColorByClick' does not exist on type '... Remove this comment to see the full error message
onColorByClick, onColorByClick,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onRemoveClick' does not exist on type '{... Remove this comment to see the full error message
onRemoveClick, onRemoveClick,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterPlotX' does not exist on type '... Remove this comment to see the full error message
isScatterPlotX, isScatterPlotX,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterPlotY' does not exist on type '... Remove this comment to see the full error message
isScatterPlotY, isScatterPlotY,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onScatterPlotXClick' does not exist on t... Remove this comment to see the full error message
onScatterPlotXClick, onScatterPlotXClick,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onScatterPlotYClick' does not exist on t... Remove this comment to see the full error message
onScatterPlotYClick, onScatterPlotYClick,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{ childre... Remove this comment to see the full error message
isObs, isObs,
}) => { }) => {
/* /*
@@ -2,11 +2,9 @@ import React, { useEffect, useRef, useState } from "react";
import { interpolateCool } from "d3-scale-chromatic"; import { interpolateCool } from "d3-scale-chromatic";
import * as d3 from "d3"; import * as d3 from "d3";
import { AxisDomain } from "d3";
import maybeScientific from "../../util/maybeScientific"; import maybeScientific from "../../util/maybeScientific";
import clamp from "../../util/clamp"; import clamp from "../../util/clamp";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
const Histogram = ({ const Histogram = ({
field, field,
fieldForId, fieldForId,
@@ -19,8 +17,8 @@ const Histogram = ({
margin, margin,
isColorBy, isColorBy,
selectionRange, selectionRange,
mini, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. mini,
}: any) => { }) => {
const svgRef = useRef(null); const svgRef = useRef(null);
const [brush, setBrush] = useState(null); const [brush, setBrush] = useState(null);
@@ -71,19 +69,14 @@ const Histogram = ({
.data(bins) .data(bins)
.enter() .enter()
.append("rect") .append("rect")
// @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
.attr("x", (d, i) => x(binStart(i)) + 1) .attr("x", (d, i) => x(binStart(i)) + 1)
.attr("y", (d) => y(d)) .attr("y", (d) => y(d))
// @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
.attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - binPadding) .attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - binPadding)
.attr("height", (d) => y(0) - y(d)) .attr("height", (d) => y(0) - y(d))
.style( .style(
"fill", "fill",
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
isColorBy isColorBy
? // @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read. ? (d, i) => colorScale(histogramScale(binStart(i)))
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(d: any, i: any) => colorScale(histogramScale(binStart(i)))
: defaultBarColor : defaultBarColor
); );
} }
@@ -108,7 +101,6 @@ const Histogram = ({
const brushXselection = container const brushXselection = container
.insert("g") .insert("g")
.attr("class", "brush") .attr("class", "brush")
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
.attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`) .attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
.call(brushX); .call(brushX);
@@ -121,12 +113,7 @@ const Histogram = ({
d3 d3
.axisBottom(x) .axisBottom(x)
.ticks(4) .ticks(4)
.tickFormat( .tickFormat(d3.format(maybeScientific(x)))
d3.format(maybeScientific(x)) as (
dv: AxisDomain,
i: number
) => string
)
); );
/* Y AXIS */ /* Y AXIS */
@@ -139,10 +126,8 @@ const Histogram = ({
.axisRight(y) .axisRight(y)
.ticks(3) .ticks(3)
.tickFormat( .tickFormat(
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
d3.format( d3.format(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
y.domain().some((n: any) => Math.abs(n) >= 10000) ? ".0e" : ","
) )
) )
); );
@@ -152,7 +137,6 @@ const Histogram = ({
svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)"); svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)");
svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)"); svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)");
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ brushX: d3.BrushBehavior<unkno... Remove this comment to see the full error message
setBrush({ brushX, brushXselection }); setBrush({ brushX, brushXselection });
} }
}, [histogram, isColorBy]); }, [histogram, isColorBy]);
@@ -162,7 +146,6 @@ const Histogram = ({
paint/update selection brush paint/update selection brush
*/ */
if (!brush) return; if (!brush) return;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'brushX' does not exist on type 'null'.
const { brushX, brushXselection } = brush; const { brushX, brushXselection } = brush;
const selection = d3.brushSelection(brushXselection.node()); const selection = d3.brushSelection(brushXselection.node());
if (!selectionRange && selection) { if (!selectionRange && selection) {
@@ -179,9 +162,7 @@ const Histogram = ({
} else { } else {
/* there is an active selection and a brush - make sure they match */ /* there is an active selection and a brush - make sure they match */
const moveDeltaThreshold = 1; const moveDeltaThreshold = 1;
// @ts-expect-error ts-migrate(2363) FIXME: The right-hand side of an arithmetic operation mus... Remove this comment to see the full error message
const dX0 = Math.abs(x0 - selection[0]); const dX0 = Math.abs(x0 - selection[0]);
// @ts-expect-error ts-migrate(2363) FIXME: The right-hand side of an arithmetic operation mus... Remove this comment to see the full error message
const dX1 = Math.abs(x1 - selection[1]); const dX1 = Math.abs(x1 - selection[1]);
/* /*
only update the brush if it is grossly incorrect, only update the brush if it is grossly incorrect,
@@ -0,0 +1,440 @@
import React from "react";
import { connect, shallowEqual } from "react-redux";
import * as d3 from "d3";
import Async from "react-async";
import memoize from "memoize-one";
import * as globals from "../../globals";
import actions from "../../actions";
import { makeContinuousDimensionName } from "../../util/nameCreators";
import HistogramHeader from "./header";
import Histogram from "./histogram";
import HistogramFooter from "./footer";
import StillLoading from "./loading";
import ErrorLoading from "./error";
const MARGIN = {
LEFT: 10, // Space for 0 tick label on X axis
RIGHT: 54, // space for Y axis & labels
BOTTOM: 25, // space for X axis & labels
TOP: 3,
};
const WIDTH = 340 - MARGIN.LEFT - MARGIN.RIGHT;
const HEIGHT = 135 - MARGIN.TOP - MARGIN.BOTTOM;
const MARGIN_MINI = {
LEFT: 0, // Space for 0 tick label on X axis
RIGHT: 0, // space for Y axis & labels
BOTTOM: 0, // space for X axis & labels
TOP: 0,
};
const WIDTH_MINI = 120 - MARGIN_MINI.LEFT - MARGIN_MINI.RIGHT;
const HEIGHT_MINI = 15 - MARGIN_MINI.TOP - MARGIN_MINI.BOTTOM;
@connect((state, ownProps) => {
const { isObs, isUserDefined, isGeneSetSummary, field } = ownProps;
const myName = makeContinuousDimensionName(
{ isObs, isUserDefined, isGeneSetSummary },
field
);
return {
annoMatrix: state.annoMatrix,
isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === field,
isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === field,
continuousSelectionRange: state.continuousSelection[myName],
isColorAccessor: state.colors.colorAccessor === field,
};
})
class HistogramBrush extends React.PureComponent {
static watchAsync(props, prevProps) {
return !shallowEqual(props.watchProps, prevProps.watchProps);
}
/* memoized closure to prevent HistogramHeader unecessary repaint */
handleColorAction = memoize((dispatch) => (field, isObs) => {
if (isObs) {
dispatch({
type: "color by continuous metadata",
colorAccessor: field,
});
} else {
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field));
}
});
onBrush = (selection, x, eventType) => {
const type = `continuous metadata histogram ${eventType}`;
return () => {
const { dispatch, field, isObs, isUserDefined, isGeneSetSummary } =
this.props;
// ignore programmatically generated events
if (!d3.event.sourceEvent) return;
// ignore cascading events, which are programmatically generated
if (d3.event.sourceEvent.sourceEvent) return;
const query = this.createQuery();
const range = d3.event.selection
? [x(d3.event.selection[0]), x(d3.event.selection[1])]
: null;
const otherProps = {
selection: field,
continuousNamespace: {
isObs,
isUserDefined,
isGeneSetSummary,
},
};
dispatch(
actions.selectContinuousMetadataAction(type, query, range, otherProps)
);
};
};
onBrushEnd = (selection, x) => () => {
const { dispatch, field, isObs, isUserDefined, isGeneSetSummary } =
this.props;
const minAllowedBrushSize = 10;
const smallAmountToAvoidInfiniteLoop = 0.1;
// ignore programmatically generated events
if (!d3.event.sourceEvent) return;
// ignore cascading events, which are programmatically generated
if (d3.event.sourceEvent.sourceEvent) return;
let type;
let range = null;
if (d3.event.selection) {
type = "continuous metadata histogram end";
if (
d3.event.selection[1] - d3.event.selection[0] >
minAllowedBrushSize
) {
range = [x(d3.event.selection[0]), x(d3.event.selection[1])];
} else {
/* the user selected range is too small and will be hidden #587, so take control of it procedurally */
/* https://stackoverflow.com/questions/12354729/d3-js-limit-size-of-brush */
const procedurallyResizedBrushWidth =
d3.event.selection[0] +
minAllowedBrushSize +
smallAmountToAvoidInfiniteLoop; //
range = [x(d3.event.selection[0]), x(procedurallyResizedBrushWidth)];
}
} else {
type = "continuous metadata histogram cancel";
}
const query = this.createQuery();
const otherProps = {
selection: field,
continuousNamespace: {
isObs,
isUserDefined,
isGeneSetSummary,
},
};
dispatch(
actions.selectContinuousMetadataAction(type, query, range, otherProps)
);
};
handleSetGeneAsScatterplotX = () => {
const { dispatch, field } = this.props;
dispatch({
type: "set scatterplot x",
data: field,
});
};
handleSetGeneAsScatterplotY = () => {
const { dispatch, field } = this.props;
dispatch({
type: "set scatterplot y",
data: field,
});
};
removeHistogram = () => {
const {
dispatch,
field,
isColorAccessor,
isScatterplotXXaccessor,
isScatterplotYYaccessor,
} = this.props;
dispatch({
type: "clear user defined gene",
data: field,
});
if (isColorAccessor) {
dispatch({
type: "reset colorscale",
});
}
if (isScatterplotXXaccessor) {
dispatch({
type: "set scatterplot x",
data: null,
});
}
if (isScatterplotYYaccessor) {
dispatch({
type: "set scatterplot y",
data: null,
});
}
};
fetchAsyncProps = async () => {
const { annoMatrix, width } = this.props;
const { isClipped } = annoMatrix;
const query = this.createQuery();
const df = await annoMatrix.fetch(...query);
const column = df.icol(0);
// if we are clipped, fetch both our value and our unclipped value,
// as we need the absolute min/max range, not just the clipped min/max.
const summary = column.summarize();
const range = [summary.min, summary.max];
let unclippedRange = [...range];
if (isClipped) {
const parent = await annoMatrix.viewOf.fetch(...query);
const { min, max } = parent.icol(0).summarize();
unclippedRange = [min, max];
}
const unclippedRangeColor = [
!annoMatrix.isClipped || annoMatrix.clipRange[0] === 0
? "#bbb"
: globals.blue,
!annoMatrix.isClipped || annoMatrix.clipRange[1] === 1
? "#bbb"
: globals.blue,
];
const histogram = this.calcHistogramCache(
column,
MARGIN,
width || WIDTH,
HEIGHT
);
const miniHistogram = this.calcHistogramCache(
column,
MARGIN_MINI,
width || WIDTH_MINI,
HEIGHT_MINI
);
const isSingleValue = summary.min === summary.max;
const nonFiniteExtent =
summary.min === undefined ||
summary.max === undefined ||
Number.isNaN(summary.min) ||
Number.isNaN(summary.max);
const OK2Render = !summary.categorical && !nonFiniteExtent;
return {
histogram,
miniHistogram,
range,
unclippedRange,
unclippedRangeColor,
isSingleValue,
OK2Render,
};
};
// eslint-disable-next-line class-methods-use-this -- instance method allows for memoization per annotation
calcHistogramCache(col, newMargin, newWidth, newHeight) {
/*
recalculate expensive stuff, notably bins, summaries, etc.
*/
const histogramCache = {}; /* maybe change this so that it computes ... */
const summary =
col.summarize(); /* this is memoized, so it's free the second time you call it */
const { min: domainMin, max: domainMax } = summary;
const numBins = 40;
const { TOP: topMargin, LEFT: leftMargin } =
newMargin; /* changes with mini */
histogramCache.domain = [
domainMin,
domainMax,
]; /* doesn't change with mini */
histogramCache.x = d3
.scaleLinear()
.domain([domainMin, domainMax])
.range([leftMargin, leftMargin + newWidth]);
histogramCache.bins = col.histogram(numBins, [
domainMin,
domainMax,
]); /* memoized */
histogramCache.binWidth = (domainMax - domainMin) / numBins;
histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth;
histogramCache.binEnd = (i) =>
domainMin + (i + 1) * histogramCache.binWidth;
const yMax = histogramCache.bins.reduce((l, r) => (l > r ? l : r));
histogramCache.y = d3
.scaleLinear()
.domain([0, yMax])
.range([topMargin + newHeight, topMargin]);
return histogramCache;
}
createQuery() {
const { isObs, isGeneSetSummary, field, setGenes, annoMatrix } = this.props;
const { schema } = annoMatrix;
if (isObs) {
return ["obs", field];
}
const varIndex = schema?.annotations?.var?.index;
if (!varIndex) return null;
if (isGeneSetSummary) {
return [
"X",
{
summarize: {
method: "mean",
field: "var",
column: varIndex,
values: [...setGenes.keys()],
},
},
];
}
// else, we assume it is a gene expression
return [
"X",
{
where: {
field: "var",
column: varIndex,
value: field,
},
},
];
}
render() {
const {
dispatch,
annoMatrix,
field,
isColorAccessor,
isUserDefined,
isGeneSetSummary,
isScatterplotXXaccessor,
isScatterplotYYaccessor,
zebra,
continuousSelectionRange,
isObs,
mini,
setGenes,
} = this.props;
let { width } = this.props;
if (!width) {
width = mini ? WIDTH_MINI : WIDTH;
}
const fieldForId = field.replace(/\s/g, "_");
const showScatterPlot = isUserDefined;
let testClass = "histogram-continuous-metadata";
if (isUserDefined) testClass = "histogram-user-gene";
else if (isGeneSetSummary) testClass = "histogram-gene-set-summary";
return (
<Async
watchFn={HistogramBrush.watchAsync}
promiseFn={this.fetchAsyncProps}
watchProps={{ annoMatrix, setGenes }}
>
<Async.Pending initial>
<StillLoading displayName={field} zebra={zebra} />
</Async.Pending>
<Async.Rejected>
{(error) => (
<ErrorLoading zebra={zebra} error={error} displayName={field} />
)}
</Async.Rejected>
<Async.Fulfilled>
{(asyncProps) =>
asyncProps.OK2Render ? (
<div
id={`histogram_${fieldForId}`}
data-testid={`histogram-${field}`}
data-testclass={testClass}
style={{
padding: mini ? 0 : globals.leftSidebarSectionPadding,
backgroundColor: zebra ? globals.lightestGrey : "white",
}}
>
{!mini && isObs ? (
<HistogramHeader
fieldId={field}
isColorBy={isColorAccessor}
isObs={isObs}
onColorByClick={this.handleColorAction(dispatch)}
onRemoveClick={isUserDefined ? this.removeHistogram : null}
isScatterPlotX={isScatterplotXXaccessor}
isScatterPlotY={isScatterplotYYaccessor}
onScatterPlotXClick={
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
}
onScatterPlotYClick={
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
}
/>
) : null}
<Histogram
field={field}
fieldForId={fieldForId}
display={asyncProps.isSingleValue ? "none" : "block"}
histogram={
mini ? asyncProps.miniHistogram : asyncProps.histogram
}
width={width}
height={mini ? HEIGHT_MINI : HEIGHT}
onBrush={this.onBrush}
onBrushEnd={this.onBrushEnd}
margin={mini ? MARGIN_MINI : MARGIN}
isColorBy={isColorAccessor}
selectionRange={continuousSelectionRange}
mini={mini}
/>
{!mini && (
<HistogramFooter
isGeneSetSummary={isGeneSetSummary}
isObs={isObs}
displayName={field}
hideRanges={asyncProps.isSingleValue}
rangeMin={asyncProps.unclippedRange[0]}
rangeMax={asyncProps.unclippedRange[1]}
rangeColorMin={asyncProps.unclippedRangeColor[0]}
rangeColorMax={asyncProps.unclippedRangeColor[1]}
/>
)}
</div>
) : null
}
</Async.Fulfilled>
</Async>
);
}
}
export default HistogramBrush;
@@ -1,547 +0,0 @@
import React from "react";
import { connect, shallowEqual } from "react-redux";
import * as d3 from "d3";
import Async from "react-async";
import memoize from "memoize-one";
import * as globals from "../../globals";
import actions from "../../actions";
import { makeContinuousDimensionName } from "../../util/nameCreators";
import HistogramHeader from "./header";
import Histogram from "./histogram";
import HistogramFooter from "./footer";
import StillLoading from "./loading";
import ErrorLoading from "./error";
import { Dataframe } from "../../util/dataframe";
const MARGIN = {
LEFT: 10, // Space for 0 tick label on X axis
RIGHT: 54, // space for Y axis & labels
BOTTOM: 25, // space for X axis & labels
TOP: 3,
};
const WIDTH = 340 - MARGIN.LEFT - MARGIN.RIGHT;
const HEIGHT = 135 - MARGIN.TOP - MARGIN.BOTTOM;
const MARGIN_MINI = {
LEFT: 0, // Space for 0 tick label on X axis
RIGHT: 0, // space for Y axis & labels
BOTTOM: 0, // space for X axis & labels
TOP: 0,
};
const WIDTH_MINI = 120 - MARGIN_MINI.LEFT - MARGIN_MINI.RIGHT;
const HEIGHT_MINI = 15 - MARGIN_MINI.TOP - MARGIN_MINI.BOTTOM;
// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message
@connect((state, ownProps) => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type '{}'.
const { isObs, isUserDefined, isGeneSetSummary, field } = ownProps;
const myName = makeContinuousDimensionName(
{ isObs, isUserDefined, isGeneSetSummary },
field
);
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
annoMatrix: (state as any).annoMatrix,
isScatterplotXXaccessor:
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(state as any).controls.scatterplotXXaccessor === field,
isScatterplotYYaccessor:
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(state as any).controls.scatterplotYYaccessor === field,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
continuousSelectionRange: (state as any).continuousSelection[myName],
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
isColorAccessor: (state as any).colors.colorAccessor === field,
};
})
class HistogramBrush extends React.PureComponent {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
static watchAsync(props: any, prevProps: any) {
return !shallowEqual(props.watchProps, prevProps.watchProps);
}
/* memoized closure to prevent HistogramHeader unecessary repaint */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
handleColorAction = memoize((dispatch) => (field: any, isObs: any) => {
if (isObs) {
dispatch({
type: "color by continuous metadata",
colorAccessor: field,
});
} else {
dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field));
}
});
// @ts-expect-error ts-migrate(6133) FIXME: 'selection' is declared but its value is never rea... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
onBrush = (selection: any, x: any, eventType: any) => {
const type = `continuous metadata histogram ${eventType}`;
return () => {
const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
dispatch,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
field,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
isObs,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
isUserDefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
isGeneSetSummary,
} = this.props;
// ignore programmatically generated events
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
if (!(d3 as any).event.sourceEvent) return;
// ignore cascading events, which are programmatically generated
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
if ((d3 as any).event.sourceEvent.sourceEvent) return;
const query = this.createQuery();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const range = (d3 as any).event.selection
? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
[x((d3 as any).event.selection[0]), x((d3 as any).event.selection[1])]
: null;
const otherProps = {
selection: field,
continuousNamespace: {
isObs,
isUserDefined,
isGeneSetSummary,
},
};
dispatch(
actions.selectContinuousMetadataAction(type, query, range, otherProps)
);
};
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
onBrushEnd =
(
_selection: any, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
x: any // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) =>
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
() => {
const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
dispatch,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
field,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
isObs,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
isUserDefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
isGeneSetSummary,
} = this.props;
const minAllowedBrushSize = 10;
const smallAmountToAvoidInfiniteLoop = 0.1;
// ignore programmatically generated events
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
if (!(d3 as any).event.sourceEvent) return;
// ignore cascading events, which are programmatically generated
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
if ((d3 as any).event.sourceEvent.sourceEvent) return;
let type;
let range = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
if ((d3 as any).event.selection) {
type = "continuous metadata histogram end";
if (
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(d3 as any).event.selection[1] - (d3 as any).event.selection[0] >
minAllowedBrushSize
) {
range = [
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
x((d3 as any).event.selection[0]),
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
x((d3 as any).event.selection[1]),
];
} else {
/* the user selected range is too small and will be hidden #587, so take control of it procedurally */
/* https://stackoverflow.com/questions/12354729/d3-js-limit-size-of-brush */
const procedurallyResizedBrushWidth =
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(d3 as any).event.selection[0] +
minAllowedBrushSize +
smallAmountToAvoidInfiniteLoop; //
range = [
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
x((d3 as any).event.selection[0]),
x(procedurallyResizedBrushWidth),
];
}
} else {
type = "continuous metadata histogram cancel";
}
const query = this.createQuery();
const otherProps = {
selection: field,
continuousNamespace: {
isObs,
isUserDefined,
isGeneSetSummary,
},
};
dispatch(
actions.selectContinuousMetadataAction(type, query, range, otherProps)
);
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
handleSetGeneAsScatterplotX = () => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
const { dispatch, field } = this.props;
dispatch({
type: "set scatterplot x",
data: field,
});
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
handleSetGeneAsScatterplotY = () => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
const { dispatch, field } = this.props;
dispatch({
type: "set scatterplot y",
data: field,
});
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
removeHistogram = () => {
const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
dispatch,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
field,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
isColorAccessor,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotXXaccessor' does not exist ... Remove this comment to see the full error message
isScatterplotXXaccessor,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotYYaccessor' does not exist ... Remove this comment to see the full error message
isScatterplotYYaccessor,
} = this.props;
dispatch({
type: "clear user defined gene",
data: field,
});
if (isColorAccessor) {
dispatch({
type: "reset colorscale",
});
}
if (isScatterplotXXaccessor) {
dispatch({
type: "set scatterplot x",
data: null,
});
}
if (isScatterplotYYaccessor) {
dispatch({
type: "set scatterplot y",
data: null,
});
}
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
fetchAsyncProps = async () => {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
const { annoMatrix, width } = this.props;
const { isClipped } = annoMatrix;
const query = this.createQuery();
// @ts-expect-error ts-migrate(2488) FIXME: Type 'any[] | null' must have a '[Symbol.iterator]... Remove this comment to see the full error message
const df: Dataframe = await annoMatrix.fetch(...query);
const column = df.icol(0);
// if we are clipped, fetch both our value and our unclipped value,
// as we need the absolute min/max range, not just the clipped min/max.
const summary = column.summarizeContinuous();
const range = [summary.min, summary.max];
let unclippedRange = [...range];
if (isClipped) {
const parent: Dataframe = await annoMatrix.viewOf.fetch(...query);
const { min, max } = parent.icol(0).summarizeContinuous();
unclippedRange = [min, max];
}
const unclippedRangeColor = [
!annoMatrix.isClipped || annoMatrix.clipRange[0] === 0
? "#bbb"
: globals.blue,
!annoMatrix.isClipped || annoMatrix.clipRange[1] === 1
? "#bbb"
: globals.blue,
];
const histogram = this.calcHistogramCache(
column,
MARGIN,
width || WIDTH,
HEIGHT
);
const miniHistogram = this.calcHistogramCache(
column,
MARGIN_MINI,
width || WIDTH_MINI,
HEIGHT_MINI
);
const isSingleValue = summary.min === summary.max;
const nonFiniteExtent =
summary.min === undefined ||
summary.max === undefined ||
Number.isNaN(summary.min) ||
Number.isNaN(summary.max);
const OK2Render = !summary.categorical && !nonFiniteExtent;
return {
histogram,
miniHistogram,
range,
unclippedRange,
unclippedRangeColor,
isSingleValue,
OK2Render,
};
};
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- instance method allows for memoization per annotation
calcHistogramCache(col: any, newMargin: any, newWidth: any, newHeight: any) {
/*
recalculate expensive stuff, notably bins, summaries, etc.
*/
const histogramCache = {}; /* maybe change this so that it computes ... */
const summary =
col.summarizeContinuous(); /* this is memoized, so it's free the second time you call it */
const { min: domainMin, max: domainMax } = summary;
const numBins = 40;
const { TOP: topMargin, LEFT: leftMargin } = newMargin;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(histogramCache as any).domain = [domainMin, domainMax];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
/* doesn't change with mini */ (histogramCache as any).x = d3
.scaleLinear()
.domain([domainMin, domainMax])
.range([leftMargin, leftMargin + newWidth]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(histogramCache as any).bins = col.histogramContinuous(numBins, [
domainMin,
domainMax,
]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
/* memoized */ (histogramCache as any).binWidth =
(domainMax - domainMin) / numBins;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(histogramCache as any).binStart = (i: any) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
domainMin + i * (histogramCache as any).binWidth;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(histogramCache as any).binEnd = (i: any) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
domainMin + (i + 1) * (histogramCache as any).binWidth;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const yMax = (histogramCache as any).bins.reduce((l: any, r: any) =>
l > r ? l : r
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(histogramCache as any).y = d3
.scaleLinear()
.domain([0, yMax])
.range([topMargin + newHeight, topMargin]);
return histogramCache;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
createQuery() {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
const { isObs, isGeneSetSummary, field, setGenes, annoMatrix } = this.props;
const { schema } = annoMatrix;
if (isObs) {
return ["obs", field];
}
const varIndex = schema?.annotations?.var?.index;
if (!varIndex) return null;
if (isGeneSetSummary) {
return [
"X",
{
summarize: {
method: "mean",
field: "var",
column: varIndex,
values: [...setGenes.keys()],
},
},
];
}
// else, we assume it is a gene expression
return [
"X",
{
where: {
field: "var",
column: varIndex,
value: field,
},
},
];
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
render() {
const {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
dispatch,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message
annoMatrix,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'field' does not exist on type 'Readonly<... Remove this comment to see the full error message
field,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isColorAccessor' does not exist on type ... Remove this comment to see the full error message
isColorAccessor,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isUserDefined' does not exist on type 'R... Remove this comment to see the full error message
isUserDefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isGeneSetSummary' does not exist on type... Remove this comment to see the full error message
isGeneSetSummary,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotXXaccessor' does not exist ... Remove this comment to see the full error message
isScatterplotXXaccessor,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterplotYYaccessor' does not exist ... Remove this comment to see the full error message
isScatterplotYYaccessor,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'zebra' does not exist on type 'Readonly<... Remove this comment to see the full error message
zebra,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'continuousSelectionRange' does not exist... Remove this comment to see the full error message
continuousSelectionRange,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isObs' does not exist on type 'Readonly<... Remove this comment to see the full error message
isObs,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'mini' does not exist on type 'Readonly<{... Remove this comment to see the full error message
mini,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'setGenes' does not exist on type 'Readon... Remove this comment to see the full error message
setGenes,
} = this.props;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'width' does not exist on type 'Readonly<... Remove this comment to see the full error message
let { width } = this.props;
if (!width) {
width = mini ? WIDTH_MINI : WIDTH;
}
const fieldForId = field.replace(/\s/g, "_");
const showScatterPlot = isUserDefined;
let testClass = "histogram-continuous-metadata";
if (isUserDefined) testClass = "histogram-user-gene";
else if (isGeneSetSummary) testClass = "histogram-gene-set-summary";
return (
<Async
watchFn={HistogramBrush.watchAsync}
promiseFn={this.fetchAsyncProps}
watchProps={{ annoMatrix, setGenes }}
>
<Async.Pending initial>
<StillLoading displayName={field} zebra={zebra} />
</Async.Pending>
<Async.Rejected>
{(error) => (
<ErrorLoading zebra={zebra} error={error} displayName={field} />
)}
</Async.Rejected>
<Async.Fulfilled>
{(asyncProps) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(asyncProps as any).OK2Render ? (
<div
id={`histogram_${fieldForId}`}
data-testid={`histogram-${field}`}
data-testclass={testClass}
style={{
padding: mini ? 0 : globals.leftSidebarSectionPadding,
backgroundColor: zebra ? globals.lightestGrey : "white",
}}
>
{!mini && isObs ? (
<HistogramHeader
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ fieldId: any; isColorBy: any; isObs: any; ... Remove this comment to see the full error message
fieldId={field}
isColorBy={isColorAccessor}
isObs={isObs}
onColorByClick={this.handleColorAction(dispatch)}
onRemoveClick={isUserDefined ? this.removeHistogram : null}
isScatterPlotX={isScatterplotXXaccessor}
isScatterPlotY={isScatterplotYYaccessor}
onScatterPlotXClick={
showScatterPlot ? this.handleSetGeneAsScatterplotX : null
}
onScatterPlotYClick={
showScatterPlot ? this.handleSetGeneAsScatterplotY : null
}
/>
) : null}
<Histogram
field={field}
fieldForId={fieldForId}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
display={(asyncProps as any).isSingleValue ? "none" : "block"}
histogram={
mini
? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(asyncProps as any).miniHistogram
: // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(asyncProps as any).histogram
}
width={width}
height={mini ? HEIGHT_MINI : HEIGHT}
onBrush={this.onBrush}
onBrushEnd={this.onBrushEnd}
margin={mini ? MARGIN_MINI : MARGIN}
isColorBy={isColorAccessor}
selectionRange={continuousSelectionRange}
mini={mini}
/>
{!mini && (
<HistogramFooter
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ isGeneSetSummary: any; isObs: any; display... Remove this comment to see the full error message
isGeneSetSummary={isGeneSetSummary}
isObs={isObs}
displayName={field}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
hideRanges={(asyncProps as any).isSingleValue}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
rangeMin={(asyncProps as any).unclippedRange[0]}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
rangeMax={(asyncProps as any).unclippedRange[1]}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
rangeColorMin={(asyncProps as any).unclippedRangeColor[0]}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
rangeColorMax={(asyncProps as any).unclippedRangeColor[1]}
/>
)}
</div>
) : null
}
</Async.Fulfilled>
</Async>
);
}
}
export default HistogramBrush;
@@ -0,0 +1,43 @@
import React from "react";
import { Button } from "@blueprintjs/core";
import * as globals from "../../globals";
const StillLoading = ({ zebra, displayName }) =>
/*
Render a loading indicator for the field.
*/
(
<div
data-testclass="gene-loading-spinner"
style={{
padding: globals.leftSidebarSectionPadding,
backgroundColor: zebra ? globals.lightestGrey : "white",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
justifyItems: "center",
alignItems: "center",
}}
>
<div style={{ minWidth: 30 }} />
<div style={{ display: "flex", alignSelf: "center" }}>
<span style={{ fontStyle: "italic" }}>{displayName}</span>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
}}
>
<Button minimal loading intent="primary" />
</div>
</div>
</div>
)
;
export default StillLoading;

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