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
parent 295590a7c6
commit eaae6df5e3
253 changed files with 11175 additions and 22271 deletions

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

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

View File

@@ -58,12 +58,10 @@ describe("metadata loads", () => {
const categories = await getAllCategoriesAndCounts(label);
expect(Object.keys(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.keys(data.categorical[label])
);
expect(Object.values(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.values(data.categorical[label])
);
}
@@ -161,12 +159,10 @@ describe("subset", () => {
const categories = await getAllCategoriesAndCounts(label);
expect(Object.keys(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.keys(data.subset.categorical[label])
);
expect(Object.values(categories)).toMatchObject(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.values(data.subset.categorical[label])
);
}
@@ -258,7 +254,6 @@ describe("centroid labels", () => {
const generatedLabels = await getAllByClass("centroid-label");
// Number of labels generated should be equal to size of the object
expect(generatedLabels).toHaveLength(
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
Object.keys(data.categorical[label]).length
);
}
@@ -280,7 +275,6 @@ describe("graph overlay", () => {
data.pan["coordinates-as-percent"]
);
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
const categoryValue = Object.keys(data.categorical[category])[0];
const initialCoordinates = await getElementCoordinates(
`${categoryValue}-centroid-label`

View File

@@ -82,8 +82,7 @@ const genesetDescriptionID =
const genesetDescriptionString = "fourth_gene_set: fourth description";
const genesetToCheckForDescription = "fourth_gene_set";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function setup(config: any) {
async function setup(config) {
await goToPage(appUrlBase);
if (config.categoricalAnno) {
@@ -157,8 +156,7 @@ describe.each([
await expect(page).toClick(getTestClass("pop-1-geneset-expand"));
await page.waitForFunction(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(selector: any) => !document.querySelector(selector),
(selector) => !document.querySelector(selector),
{},
getTestClass("gene-loading-spinner")
);
@@ -173,8 +171,7 @@ describe.each([
await expect(page).toClick(getTestClass("pop-2-geneset-expand"));
await page.waitForFunction(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(selector: any) => !document.querySelector(selector),
(selector) => !document.querySelector(selector),
{},
getTestClass("gene-loading-spinner")
);
@@ -405,13 +402,8 @@ describe.each([
expect(actualLabelName).toBe(expectedLabelName);
expect(actualLabelCount).toBe(expectedLabelCount);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function getInnerText(element: any, className: any) {
return element.$eval(
getTestClass(className),
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(node: any) => node?.innerText
);
async function getInnerText(element, className) {
return element.$eval(getTestClass(className), (node) => node?.innerText);
}
});
@@ -436,9 +428,7 @@ describe.each([
`categorical-value-count-${perTestCategoryName}-${perTestLabelName}`
);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
expect(await result.evaluate((node) => node.innerText)).toBe(
// @ts-expect-error ts-migrate(2538) FIXME: Type 'boolean' cannot be used as an index type.
data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
);
});
@@ -498,7 +488,6 @@ describe.each([
await createLabel(perTestCategoryName, labelName);
await assertLabelExists(perTestCategoryName, labelName);
await clickOn("undo");
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName);
await clickOn("redo");
await assertLabelExists(perTestCategoryName, labelName);
@@ -508,12 +497,10 @@ describe.each([
await setup(config);
await deleteLabel(perTestCategoryName, perTestLabelName);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName);
await clickOn("undo");
await assertLabelExists(perTestCategoryName, perTestLabelName);
await clickOn("redo");
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
await assertLabelDoesNotExist(perTestCategoryName);
});
@@ -544,9 +531,7 @@ describe.each([
const labels = await getAllByClass("categorical-row");
const result = await Promise.all(
labels.map((label) =>
page.evaluate((element) => element.outerHTML, label)
)
labels.map((label) => page.evaluate((element) => element.outerHTML, label))
);
expect(result).toMatchSnapshot();
@@ -574,11 +559,9 @@ describe.each([
expect(result).toMatchSnapshot();
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function assertCategoryExists(categoryName: any) {
async function assertCategoryExists(categoryName) {
const handle = await waitByID(`${categoryName}:category-label`);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const result = await handle.evaluate((node) =>
node.getAttribute("aria-label")
);
@@ -586,8 +569,7 @@ describe.each([
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: any, labelName: any) {
async function assertLabelExists(categoryName, labelName) {
await expect(page).toMatchElement(
getTestId(`${categoryName}:category-expand`)
);
@@ -599,13 +581,11 @@ describe.each([
);
expect(
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
await previous.evaluate((node) => node.getAttribute("aria-label"))
).toBe(labelName);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function assertLabelDoesNotExist(categoryName: any, labelName: any) {
async function assertLabelDoesNotExist(categoryName, labelName) {
await expandCategory(categoryName);
const result = await page.$(
`[data-testid='categorical-value-${categoryName}-${labelName}']`

View File

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

View File

@@ -23,7 +23,6 @@ beforeEach(async () => {
const userAgent = await browser.userAgent();
await page.setUserAgent(`${userAgent}bot`);
// @ts-expect-error ts-migrate(2341) FIXME: Property '_client' is private and only accessible ... Remove this comment to see the full error message
await page._client.send("Animation.setPlaybackRate", { playbackRate: 12 });
page.on("pageerror", (err) => {
@@ -50,8 +49,7 @@ beforeEach(async () => {
}
const errorMsgText = await Promise.all(
// 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: any) => arg._remoteObject.description)
msg.args().map((arg) => arg._remoteObject.description)
);
throw new Error(`Console error: ${errorMsgText}`);
}

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

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

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");
require("jest-circus");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const ENV_DEFAULT = require("../../../environment.default.json");
// @ts-expect-error ts-migrate(2451) FIXME: Cannot redeclare block-scoped variable 'takeScreen... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const takeScreenshot = require("./takeScreenshot");
class ScreenshotEnvironment extends PuppeteerEnvironment {

View File

@@ -1,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 {
SecretsManagerClient,
GetSecretValueCommand,
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
} = require("@aws-sdk/client-secrets-manager");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const { setup } = require("jest-environment-puppeteer");
const client = new SecretsManagerClient({ region: "us-west-2" });

View File

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

View File

@@ -501,7 +501,6 @@ describe("geneset: set tid", () => {
test("not a number error", () => {
expect(() => {
genesetsReducer(
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number' is not assignable to type 'undefined... Remove this comment to see the full error message
{ lastTid: 1 },
{
type: "geneset: set tid",
@@ -514,7 +513,6 @@ describe("geneset: set tid", () => {
test("decrement error", () => {
expect(() => {
genesetsReducer(
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number' is not assignable to type 'undefined... Remove this comment to see the full error message
{ lastTid: 1 },
{
type: "geneset: set tid",

View File

@@ -1,12 +1,9 @@
import { Reducer } from "redux";
import undoable from "../../src/reducers/undoable";
describe("create", () => {
test("no keys", () => {
expect(() =>
undoable(() => {}, undefined as unknown as string[])
).toThrow();
expect(() => undoable(() => {}, null as unknown as string[])).toThrow();
expect(() => undoable(() => {})).toThrow();
expect(() => undoable(() => {}, null)).toThrow();
expect(() => undoable(() => {}, [])).toThrow();
expect(() => undoable(() => {}, [], {})).toThrow();
});
@@ -26,7 +23,7 @@ describe("create", () => {
describe("undo", () => {
test("expected state modifications", () => {
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 s1 = undoableReducer(initialState, { type: "test" });
@@ -44,8 +41,8 @@ describe("undo", () => {
describe("redo", () => {
const initialState = { a: 0, b: 1000 };
const reducer: Reducer = (state) => ({ a: state.a + 1, b: state.b + 1 });
let UR: Reducer;
const reducer = (state) => ({ a: state.a + 1, b: state.b + 1 });
let UR;
beforeEach(() => {
UR = undoable(reducer, ["a"]);

View File

@@ -5,6 +5,5 @@ the jest test environment).
import { TextDecoder, TextEncoder } from "util";
// @ts-expect-error ts-migrate(2322) FIXME: Type 'typeof TextDecoder' is not assignable to typ... Remove this comment to see the full error message
global.TextDecoder = TextDecoder;
global.TextEncoder = TextEncoder;

View File

@@ -11,18 +11,14 @@ describe("rangeEncodeIndices", () => {
test("sorted flag", () => {
expect(rangeEncodeIndices([1, 9, 432], 10, true)).toMatchObject([
1,
9,
432,
1, 9, 432,
]);
expect(rangeEncodeIndices([1, 9, 432], 10, false)).toMatchObject([
1,
9,
432,
1, 9, 432,
]);
expect(
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true)
).toMatchObject([[0, 3], [9, 10], 432]);
expect(rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, true)).toMatchObject(
[[0, 3], [9, 10], 432]
);
expect(
rangeEncodeIndices([0, 1, 2, 3, 9, 10, 432], 2, false)
).toMatchObject([[0, 3], [9, 10], 432]);

View File

@@ -10,18 +10,14 @@ import {
isubsetMask,
} from "../../../src/annoMatrix";
import { Dataframe } from "../../../src/util/dataframe";
import { Field } from "../../../src/common/types/schema";
enableFetchMocks();
describe("AnnoMatrix", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let annoMatrix: any;
let annoMatrix;
beforeEach(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).resetMocks(); // reset all fetch mocking state
// reset all fetch mocking state
fetch.resetMocks(); // reset all fetch mocking state
annoMatrix = new AnnoMatrixLoader(
serverMocks.baseDataURL,
serverMocks.schema.schema
@@ -35,67 +31,58 @@ describe("AnnoMatrix", () => {
expect(annoMatrix.nObs).toEqual(serverMocks.schema.schema.dataframe.nObs);
expect(annoMatrix.nVar).toEqual(serverMocks.schema.schema.dataframe.nVar);
expect(annoMatrix.isView).toBeFalsy();
expect(annoMatrix.viewOf).toBe(annoMatrix);
expect(annoMatrix.viewOf).toBeUndefined();
expect(annoMatrix.rowIndex).toBeDefined();
});
test("simple single column fetch", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.annotationsObs(["name_0"]));
fetch.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.colIndex.labels()).toEqual(["name_0"]);
expect(df.dims).toEqual([annoMatrix.nObs, 1]);
});
test("simple multi column fetch", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any)
fetch
.once(serverMocks.annotationsObs(["name_0"]))
.once(serverMocks.annotationsObs(["n_genes"]));
await expect(
annoMatrix.fetch(Field.obs, ["name_0", "n_genes"])
annoMatrix.fetch("obs", ["name_0", "n_genes"])
).resolves.toBeInstanceOf(Dataframe);
});
describe("fetch from field", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const getLastTwo = async (field: any) => {
const getLastTwo = async (field) => {
const columnNames = annoMatrix.getMatrixColumns(field).slice(-2);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockResponses(
...columnNames.map(() => serverMocks.responder)
);
fetch.mockResponses(...columnNames.map(() => serverMocks.responder));
await expect(
annoMatrix.fetch(field, columnNames)
).resolves.toBeInstanceOf(Dataframe);
};
test(Field.obs, async () => getLastTwo(Field.obs));
test("obs", async () => getLastTwo("obs"));
test("var", async () => getLastTwo("var"));
test("emb", async () => getLastTwo("emb"));
});
test("fetch - test all query forms", async () => {
// single string is a column name
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.annotationsObs(["n_genes"]));
await expect(
annoMatrix.fetch(Field.obs, "n_genes")
).resolves.toBeInstanceOf(Dataframe);
fetch.once(serverMocks.annotationsObs(["n_genes"]));
await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf(
Dataframe
);
// array of column names, expecting n_genes to be cached.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.annotationsObs(["percent_mito"]));
fetch.once(serverMocks.annotationsObs(["percent_mito"]));
await expect(
annoMatrix.fetch(Field.obs, ["n_genes", "percent_mito"])
annoMatrix.fetch("obs", ["n_genes", "percent_mito"])
).resolves.toBeInstanceOf(Dataframe);
// more complex value filter query, enumerated
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(serverMocks.responder);
fetch.once(serverMocks.responder);
await expect(
annoMatrix.fetch("X", {
where: {
@@ -108,8 +95,7 @@ describe("AnnoMatrix", () => {
// more complex value filter query, range
const varIndex = annoMatrix.schema.annotations.var.index;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any)
fetch
.once(
serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]])
)
@@ -152,9 +138,9 @@ describe("AnnoMatrix", () => {
test("schema accessors", () => {
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(annoMatrix.getColumnSchema("emb", "umap")).toEqual({
@@ -172,7 +158,7 @@ describe("AnnoMatrix", () => {
test the mask & label access to subset via isubset and isubsetMask
*/
test("isubset", async () => {
const rowList = new Int32Array([0, 10]);
const rowList = [0, 10];
const rowMask = new Uint8Array(annoMatrix.nObs);
for (let i = 0; i < rowList.length; i += 1) {
rowMask[rowList[i]] = 1;
@@ -185,14 +171,11 @@ describe("AnnoMatrix", () => {
expect(am1.nObs).toEqual(am2.nObs);
expect(am1.nVar).toEqual(am2.nVar);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any)
fetch
.once(serverMocks.annotationsObs(["n_genes"]))
.once(serverMocks.annotationsObs(["n_genes"]));
const ng1 = (await am1.fetch(Field.obs, "n_genes")) as Dataframe;
const ng2 = (await am2.fetch(Field.obs, "n_genes")) as Dataframe;
expect(ng1).toBeDefined();
expect(ng2).toBeDefined();
const ng1 = await am1.fetch("obs", "n_genes");
const ng2 = await am2.fetch("obs", "n_genes");
expect(ng1).toHaveLength(ng2.length);
expect(ng1.colIndex.labels()).toEqual(ng2.colIndex.labels());
expect(ng1.col("n_genes").asArray()).toEqual(
@@ -202,12 +185,10 @@ describe("AnnoMatrix", () => {
});
describe("add/drop column", () => {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
async function addDrop(base) {
expect(base.getMatrixColumns(Field.obs)).not.toContain("foo");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(base.fetch(Field.obs, "foo")).rejects.toThrow(
expect(base.getMatrixColumns("obs")).not.toContain("foo");
fetch.mockRejectOnce(new Error("unknown column name"));
await expect(base.fetch("obs", "foo")).rejects.toThrow(
"unknown column name"
);
@@ -217,9 +198,9 @@ describe("AnnoMatrix", () => {
Float32Array,
0
);
expect(base.getMatrixColumns(Field.obs)).not.toContain("foo");
expect(am1.getMatrixColumns(Field.obs)).toContain("foo");
const foo: Dataframe = await am1.fetch(Field.obs, "foo");
expect(base.getMatrixColumns("obs")).not.toContain("foo");
expect(am1.getMatrixColumns("obs")).toContain("foo");
const foo = await am1.fetch("obs", "foo");
expect(foo).toBeDefined();
expect(foo).toBeInstanceOf(Dataframe);
expect(foo).toHaveLength(am1.nObs);
@@ -229,11 +210,10 @@ describe("AnnoMatrix", () => {
/* drop */
const am2 = am1.dropObsColumn("foo");
expect(base.getMatrixColumns(Field.obs)).not.toContain("foo");
expect(am2.getMatrixColumns(Field.obs)).not.toContain("foo");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(am2.fetch(Field.obs, "foo")).rejects.toThrow(
expect(base.getMatrixColumns("obs")).not.toContain("foo");
expect(am2.getMatrixColumns("obs")).not.toContain("foo");
fetch.mockRejectOnce(new Error("unknown column name"));
await expect(am2.fetch("obs", "foo")).rejects.toThrow(
"unknown column name"
);
}
@@ -246,25 +226,23 @@ describe("AnnoMatrix", () => {
const am1 = clip(annoMatrix, 0.1, 0.9);
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);
const am3 = isubset(annoMatrix, new Int32Array([10, 0, 7, 3]));
const am3 = isubset(annoMatrix, [10, 0, 7, 3]);
await addDrop(am3);
const am4 = clip(am3, 0, 1);
await addDrop(am4);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockResponse(serverMocks.responder);
fetch.mockResponse(serverMocks.responder);
await am1.fetch(Field.obs, am1.getMatrixColumns(Field.obs));
await am2.fetch(Field.obs, am2.getMatrixColumns(Field.obs));
await am3.fetch(Field.obs, am3.getMatrixColumns(Field.obs));
await am4.fetch(Field.obs, am4.getMatrixColumns(Field.obs));
await am1.fetch("obs", am1.getMatrixColumns("obs"));
await am2.fetch("obs", am2.getMatrixColumns("obs"));
await am3.fetch("obs", am3.getMatrixColumns("obs"));
await am4.fetch("obs", am4.getMatrixColumns("obs"));
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).resetMocks();
fetch.resetMocks();
await addDrop(am1);
await addDrop(am2);
@@ -274,7 +252,6 @@ describe("AnnoMatrix", () => {
});
describe("setObsColumnValues", () => {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'base' implicitly has an 'any' type.
async function addSetDrop(base) {
/* add column */
let am = base.addObsColumn(
@@ -288,7 +265,7 @@ describe("AnnoMatrix", () => {
"unassigned"
);
const testVal = await am.fetch(Field.obs, "test");
const testVal = await am.fetch("obs", "test");
expect(testVal.col("test").asArray()).toEqual(
new Array(am.nObs).fill("unassigned")
);
@@ -296,7 +273,7 @@ describe("AnnoMatrix", () => {
/* set values in column */
const whichRows = [1, 2, 10];
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");
for (let i = 0; i < whichRows.length; i += 1) {
const offset = am1.rowIndex.getOffset(whichRows[i]);
@@ -304,16 +281,15 @@ describe("AnnoMatrix", () => {
}
expect(testVal1).not.toBe(testVal);
expect(testVal1.col("test").asArray()).toEqual(expt);
expect(am1.getColumnSchema(Field.obs, "test").type).toBe("categorical");
expect(am1.getColumnSchema(Field.obs, "test").categories).toEqual(
expect(am1.getColumnSchema("obs", "test").type).toBe("categorical");
expect(am1.getColumnSchema("obs", "test").categories).toEqual(
expect.arrayContaining(["unassigned", "red", "green", "yo"])
);
/* drop column */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
fetch.mockRejectOnce(new Error("unknown column name"));
am = am1.dropObsColumn("test");
await expect(am.fetch(Field.obs, "test")).rejects.toThrow(
await expect(am.fetch("obs", "test")).rejects.toThrow(
"unknown column name"
);
}
@@ -326,18 +302,17 @@ describe("AnnoMatrix", () => {
const am1 = clip(annoMatrix, 0.1, 0.9);
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);
const am3 = isubset(annoMatrix, new Int32Array([10, 1, 0, 30, 2]));
const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]);
await addSetDrop(am3);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockResponse(serverMocks.responder);
fetch.mockResponse(serverMocks.responder);
await am1.fetch(Field.obs, am1.getMatrixColumns(Field.obs));
await am2.fetch(Field.obs, am2.getMatrixColumns(Field.obs));
await am3.fetch(Field.obs, am3.getMatrixColumns(Field.obs));
await am1.fetch("obs", am1.getMatrixColumns("obs"));
await am2.fetch("obs", am2.getMatrixColumns("obs"));
await am3.fetch("obs", am3.getMatrixColumns("obs"));
await addSetDrop(am1);
await addSetDrop(am2);

View File

@@ -12,25 +12,19 @@ import {
AnnoMatrixObsCrossfilter,
isubsetMask,
} from "../../../src/annoMatrix";
import { Dataframe } from "../../../src/util/dataframe";
import { rangeFill } from "../../../src/util/range";
import { Field, Schema } from "../../../src/common/types/schema";
enableFetchMocks();
describe("AnnoMatrixCrossfilter", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let annoMatrix: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let crossfilter: any;
let annoMatrix;
let crossfilter;
beforeEach(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).resetMocks(); // reset all fetch mocking state
// reset all fetch mocking state
fetch.resetMocks(); // reset all fetch mocking state
annoMatrix = new AnnoMatrixLoader(
serverMocks.baseDataURL,
serverMocks.schema.schema as Schema
serverMocks.schema.schema
);
crossfilter = new AnnoMatrixObsCrossfilter(annoMatrix);
});
@@ -73,11 +67,8 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.obsCrossfilter.hasDimension("obs/louvain")
).toBeFalsy();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
let newCrossfilter = await crossfilter.select(Field.obs, "louvain", {
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
let newCrossfilter = await crossfilter.select("obs", "louvain", {
mode: "none",
});
@@ -85,10 +76,9 @@ describe("AnnoMatrixCrossfilter", () => {
expect(
newCrossfilter.obsCrossfilter.hasDimension("obs/louvain")
).toBeTruthy();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
expect((fetch as any).mock.calls).toHaveLength(1);
expect(fetch.mock.calls).toHaveLength(1);
newCrossfilter = await crossfilter.select(Field.obs, "louvain", {
newCrossfilter = await crossfilter.select("obs", "louvain", {
mode: "all",
});
expect(newCrossfilter.countSelected()).toEqual(annoMatrix.nObs);
@@ -97,11 +87,8 @@ describe("AnnoMatrixCrossfilter", () => {
test("simple column select", async () => {
let xfltr;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
xfltr = await crossfilter.select(Field.obs, "louvain", {
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
xfltr = await crossfilter.select("obs", "louvain", {
mode: "exact",
values: ["NK cells", "B cells"],
});
@@ -118,7 +105,6 @@ describe("AnnoMatrixCrossfilter", () => {
expect(xfltr.allSelectedLabels()).toEqual(
Int32Array.from(
obsLouvain.reduce((acc, val, idx) => {
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message
if (val === "NK cells" || val === "B cells") acc.push(idx);
return acc;
}, [])
@@ -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 selected = xfltr.allSelectedMask();
values.every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(val: any, idx: any) =>
!["NK cells", "B cells"].includes(val) !== !selected[idx]
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
fetch.once(
serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)])
);
xfltr = await xfltr.select(Field.obs, "n_genes", {
xfltr = await xfltr.select("obs", "n_genes", {
mode: "range",
lo: 0,
hi: 500,
@@ -163,7 +146,6 @@ describe("AnnoMatrixCrossfilter", () => {
val < 500 &&
(louvain === "NK cells" || louvain === "B cells")
)
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number' is not assignable to par... Remove this comment to see the full error message
acc.push(idx);
return acc;
}, [])
@@ -178,8 +160,7 @@ describe("AnnoMatrixCrossfilter", () => {
const varIndex = annoMatrix.schema.annotations.var.index;
const { nObs } = annoMatrix.schema.dataframe;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
fetch.once(
serverMocks.dataframeResponse(
["TEST"],
[rangeFill(new Float32Array(nObs), 0, 0.1)]
@@ -215,19 +196,14 @@ describe("AnnoMatrixCrossfilter", () => {
});
const values = df.icol(0).asArray();
const selected = xfltr.allSelectedMask();
values.every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(val: any, idx: any) => !(val >= 0 && val <= 50) !== !selected[idx]
values.every((val, idx) => !(val >= 0 && val <= 50) !== !selected[idx]);
expect(selected.reduce((acc, val) => (val ? acc + 1 : acc), 0)).toEqual(
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 () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
fetch.once(
serverMocks.dataframeResponse(
["umap_0", "umap_1"],
[Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])]
@@ -246,7 +222,6 @@ describe("AnnoMatrixCrossfilter", () => {
test("select on subset", async () => {
const mask = new Uint8Array(annoMatrix.nObs).fill(0);
for (let i = 0; i < mask.length; i += 2) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'number'.
mask[i] = true;
}
const annoMatrixSubset = isubsetMask(annoMatrix, mask);
@@ -255,11 +230,8 @@ describe("AnnoMatrixCrossfilter", () => {
let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset);
expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
xfltr = await xfltr.select(Field.obs, "louvain", {
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
xfltr = await xfltr.select("obs", "louvain", {
mode: "exact",
values: ["NK cells", "B cells"],
});
@@ -267,17 +239,11 @@ describe("AnnoMatrixCrossfilter", () => {
expect(xfltr).toBeDefined();
expect(xfltr.countSelected()).toEqual(240);
const df: Dataframe = (await annoMatrixSubset.fetch(
Field.obs,
"louvain"
)) as Dataframe;
expect(df).toBeDefined();
const df = await annoMatrixSubset.fetch("obs", "louvain");
const values = df.col("louvain").asArray();
const selected = xfltr.allSelectedMask();
values.every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(val: any, idx: any) =>
!["NK cells", "B cells"].includes(val) !== !selected[idx]
(val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx]
);
});
@@ -290,9 +256,8 @@ describe("AnnoMatrixCrossfilter", () => {
"unable to obsSelect upon the var dimension"
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(crossfilter.select(Field.obs, "foo")).rejects.toThrow(
fetch.mockRejectOnce(new Error("unknown column name"));
await expect(crossfilter.select("obs", "foo")).rejects.toThrow(
"unknown column name"
);
});
@@ -302,32 +267,27 @@ describe("AnnoMatrixCrossfilter", () => {
/*
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: any, colName: any, colSchema = null) {
async function helperAddTestCol(cf, colName, colSchema = null) {
expect(
cf.annoMatrix.getMatrixColumns(Field.obs).includes(colName)
cf.annoMatrix.getMatrixColumns("obs").includes(colName)
).toBeFalsy();
if (colSchema === null) {
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ name: any; type: string; categories: strin... Remove this comment to see the full error message
colSchema = {
name: colName,
type: "categorical",
categories: ["toasty"],
};
}
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
colSchema.name = colName;
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
const initValue = colSchema.categories[0];
const xfltr = cf.addObsColumn(colSchema, Array, initValue);
expect(
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any) => v.name === colName
(v) => v.name === colName
)
).toHaveLength(1);
const df = await xfltr.annoMatrix.fetch(Field.obs, colName);
const df = await xfltr.annoMatrix.fetch("obs", colName);
expect(df.hasCol(colName)).toBeTruthy();
return xfltr;
}
@@ -335,7 +295,7 @@ describe("AnnoMatrixCrossfilter", () => {
test("addObsColumn", async () => {
expect(crossfilter.countSelected()).toBe(annoMatrix.nObs);
expect(
crossfilter.annoMatrix.getMatrixColumns(Field.obs).includes("foo")
crossfilter.annoMatrix.getMatrixColumns("obs").includes("foo")
).toBeFalsy();
const xfltr = crossfilter.addObsColumn(
{ name: "foo", type: "categorical", categories: ["A"] },
@@ -346,7 +306,7 @@ describe("AnnoMatrixCrossfilter", () => {
// check schema updates correctly.
expect(xfltr.countSelected()).toBe(annoMatrix.nObs);
expect(
xfltr.annoMatrix.getMatrixColumns(Field.obs).includes("foo")
xfltr.annoMatrix.getMatrixColumns("obs").includes("foo")
).toBeTruthy();
expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toMatchObject({
name: "foo",
@@ -354,18 +314,17 @@ describe("AnnoMatrixCrossfilter", () => {
});
expect(
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any) => v.name === "foo"
(v) => v.name === "foo"
)
).toHaveLength(1);
// check data update.
const df: Dataframe = await xfltr.annoMatrix.fetch(Field.obs, "foo");
const df = await xfltr.annoMatrix.fetch("obs", "foo");
expect(
df
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "A")
.asArray()
.every((v) => v === "A")
).toBeTruthy();
// check that we catch dups
@@ -402,30 +361,27 @@ describe("AnnoMatrixCrossfilter", () => {
xfltr = xfltr.dropObsColumn("foo");
expect(
xfltr.annoMatrix.schema.annotations.obs.columns.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any) => v.name === "foo"
(v) => v.name === "foo"
)
).toHaveLength(0);
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 as any).mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.annoMatrix.fetch(Field.obs, "foo")).rejects.toThrow(
fetch.mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
"unknown column name"
);
// now same, but ensure we have built an index before doing the drop
xfltr = await helperAddTestCol(crossfilter, "bar");
xfltr = await xfltr.select(Field.obs, "bar", {
xfltr = await xfltr.select("obs", "bar", {
mode: "exact",
values: "whatever",
});
xfltr = xfltr.dropObsColumn("bar");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(
xfltr.select(Field.obs, "bar", { mode: "all" })
).rejects.toThrow("unknown column name");
fetch.mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
"unknown column name"
);
});
test("renameObsColumn", async () => {
@@ -442,43 +398,38 @@ describe("AnnoMatrixCrossfilter", () => {
// add a column, then rename it.
xfltr = await helperAddTestCol(crossfilter, "foo");
xfltr = xfltr.renameObsColumn("foo", "bar");
expect(
xfltr.annoMatrix.getColumnSchema(Field.obs, "foo")
).toBeUndefined();
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "bar")).toMatchObject({
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toBeUndefined();
expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({
name: "bar",
type: "categorical",
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.annoMatrix.fetch(Field.obs, "foo")).rejects.toThrow(
fetch.mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow(
"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();
// now same, but ensure we have built an index before doing the rename
xfltr = await helperAddTestCol(crossfilter, "bar");
xfltr = await xfltr.select(Field.obs, "bar", {
xfltr = await xfltr.select("obs", "bar", {
mode: "exact",
values: "whatever",
});
xfltr = xfltr.renameObsColumn("bar", "xyz");
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).mockRejectOnce(new Error("unknown column name"));
fetch.mockRejectOnce(new Error("unknown column name"));
await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow(
"unknown column name"
);
await expect(
xfltr.select(Field.obs, "bar", { mode: "all" })
).rejects.toThrow("unknown column name");
await expect(
xfltr.select(Field.obs, "xyz", { mode: "none" })
xfltr.select("obs", "xyz", { mode: "none" })
).resolves.toBeInstanceOf(AnnoMatrixObsCrossfilter);
});
test("addObsAnnoCategory", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let xfltr: any;
let xfltr;
// catch unknown or readonly columns
expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow(
@@ -489,14 +440,13 @@ describe("AnnoMatrixCrossfilter", () => {
).toThrow("Unknown or readonly obs column");
// add a column and then add category to it
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo",
type: "categorical",
categories: ["unassigned"],
});
xfltr = xfltr.addObsAnnoCategory("foo", "a-new-label");
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject({
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
type: "categorical",
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
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
xfltr = await helperAddTestCol(crossfilter, "bar", {
name: "bar",
type: "categorical",
categories: ["unassigned"],
});
xfltr = await xfltr.select(Field.obs, "bar", {
xfltr = await xfltr.select("obs", "bar", {
mode: "exact",
values: "something",
});
xfltr = xfltr.addObsAnnoCategory("bar", "a-new-label");
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "bar")).toMatchObject({
expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({
name: "bar",
type: "categorical",
categories: expect.arrayContaining(["a-new-label", "unassigned"]),
@@ -537,20 +486,19 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.removeObsAnnoCategory("undefined-name", "mumble")
).rejects.toThrow("Unknown or readonly obs column");
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo",
type: "categorical",
categories: ["unassigned", "red", "green", "blue"],
});
xfltr = await xfltr.select(Field.obs, "foo", { mode: "all" });
xfltr = await xfltr.select("obs", "foo", { mode: "all" });
expect(
(await xfltr.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "unassigned")
.asArray()
.every((v) => v === "unassigned")
).toBeTruthy();
expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject({
expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
type: "categorical",
categories: expect.arrayContaining([
@@ -564,13 +512,12 @@ describe("AnnoMatrixCrossfilter", () => {
// remove an unused category
const xfltr1 = await xfltr.removeObsAnnoCategory("foo", "red", "mumble");
expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "unassigned")
.asArray()
.every((v) => v === "unassigned")
).toBeTruthy();
expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject(
{
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
type: "categorical",
categories: expect.arrayContaining([
@@ -579,8 +526,7 @@ describe("AnnoMatrixCrossfilter", () => {
"blue",
"mumble",
]),
}
);
});
// remove a used category
const xfltr2 = await xfltr.removeObsAnnoCategory(
@@ -589,18 +535,16 @@ describe("AnnoMatrixCrossfilter", () => {
"red"
);
expect(
(await xfltr2.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr2.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "red")
.asArray()
.every((v) => v === "red")
).toBeTruthy();
expect(xfltr2.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject(
{
expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
type: "categorical",
categories: expect.arrayContaining(["green", "blue", "red"]),
}
);
});
});
test("setObsColumnValues", async () => {
@@ -612,13 +556,12 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.setObsColumnValues("undefined-name", [0], "mumble")
).rejects.toThrow("Unknown or readonly obs column");
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
let xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo",
type: "categorical",
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
await expect(() =>
@@ -627,24 +570,22 @@ describe("AnnoMatrixCrossfilter", () => {
// set a few rows
expect(
(await xfltr.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.every((v: any) => v === "unassigned")
.asArray()
.every((v) => v === "unassigned")
).toBeTruthy();
const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray()
.every(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(v: any, i: any) =>
(v, i) =>
v === "unassigned" || (v === "purple" && (i === 0 || i === 10))
)
).toBeTruthy();
expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject(
{
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
type: "categorical",
categories: expect.arrayContaining([
@@ -654,16 +595,15 @@ describe("AnnoMatrixCrossfilter", () => {
"blue",
"purple",
]),
}
);
});
expect(xfltr1.countSelected()).toEqual(xfltr1.annoMatrix.nObs);
const xfltr2 = await xfltr1.select(Field.obs, "foo", {
const xfltr2 = await xfltr1.select("obs", "foo", {
mode: "exact",
values: ["purple"],
});
expect(xfltr2.countSelected()).toEqual(2);
expect(xfltr2.allSelectedLabels()).toEqual(Array.from([0, 10]));
expect(xfltr2.allSelectedLabels()).toEqual(Int32Array.from([0, 10]));
});
test("resetObsColumnValues", async () => {
@@ -675,13 +615,12 @@ describe("AnnoMatrixCrossfilter", () => {
crossfilter.resetObsColumnValues("undefined-name", "red", "blue")
).rejects.toThrow("Unknown or readonly obs column");
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: string; type: string; ca... Remove this comment to see the full error message
let xfltr = await helperAddTestCol(crossfilter, "foo", {
name: "foo",
type: "categorical",
categories: ["unassigned", "red", "green", "blue"],
});
xfltr = await xfltr.select(Field.obs, "foo", {
xfltr = await xfltr.select("obs", "foo", {
mode: "exact",
values: "red",
});
@@ -692,32 +631,31 @@ describe("AnnoMatrixCrossfilter", () => {
).rejects.toThrow("unknown category");
let xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple");
xfltr1 = await xfltr1.select(Field.obs, "foo", {
xfltr1 = await xfltr1.select("obs", "foo", {
mode: "exact",
values: "purple",
});
expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v: any) => v === "purple")
.asArray()
.filter((v) => v === "purple")
).toHaveLength(2);
xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta");
expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v: any) => v === "magenta")
.asArray()
.filter((v) => v === "magenta")
).toHaveLength(2);
expect(
(await xfltr1.annoMatrix.fetch(Field.obs, "foo"))
(await xfltr1.annoMatrix.fetch("obs", "foo"))
.col("foo")
.asArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.filter((v: any) => v === "purple")
.asArray()
.filter((v) => v === "purple")
).toHaveLength(0);
expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject(
{
expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({
name: "foo",
type: "categorical",
categories: expect.arrayContaining([
@@ -728,24 +666,19 @@ describe("AnnoMatrixCrossfilter", () => {
"purple",
"magenta",
]),
}
);
});
});
});
describe("edge cases", () => {
test("transition from empty annoMatrix", async () => {
// select before fetch needs to work
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(fetch as any).once(
serverMocks.dataframeResponse(["louvain"], [obsLouvain])
);
const xfltr = await crossfilter.select(Field.obs, "louvain", {
fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain]));
const xfltr = await crossfilter.select("obs", "louvain", {
mode: "exact",
values: "B cells",
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
expect((fetch as any).mock.calls).toHaveLength(1);
expect(fetch.mock.calls).toHaveLength(1);
expect(xfltr.obsCrossfilter.hasDimension("obs/louvain")).toBeTruthy();
expect(xfltr.obsCrossfilter.all()).toBe(xfltr.annoMatrix._cache.obs);
expect(xfltr.countSelected()).toEqual(

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
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 as any).CELLXGENE = {
window.CELLXGENE = {
API: {
prefix: baseDataURL,
version: "v0.2/",

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
import { RawSchema } from "../../../../src/common/types/schema";
export const schema: { schema: RawSchema } = {
export const schema = {
schema: {
annotations: {
obs: {

File diff suppressed because it is too large Load Diff

View File

@@ -4,34 +4,13 @@ import {
_whereCacheCreate,
_whereCacheMerge,
} 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", () => {
test("whereCacheGet - where query, missing cache values", () => {
expect(
_whereCacheGet({}, schema, Field.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, {
_whereCacheGet({}, schema, "X", {
where: {
field: "var",
column: "foo",
@@ -40,7 +19,25 @@ describe("whereCache", () => {
})
).toEqual([undefined]);
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: {
field: "var",
column: "foo",
@@ -52,7 +49,7 @@ describe("whereCache", () => {
_whereCacheGet(
{ where: { X: { var: new Map([["foo", new Map()]]) } } },
schema,
Field.X,
"X",
{
where: {
field: "var",
@@ -66,7 +63,7 @@ describe("whereCache", () => {
test("whereCacheGet - summarize query, missing cache values", () => {
expect(
_whereCacheGet({}, schema, Field.X, {
_whereCacheGet({}, schema, "X", {
summarize: {
method: "mean",
field: "var",
@@ -79,7 +76,7 @@ describe("whereCache", () => {
_whereCacheGet(
{ summarize: { X: { mean: { var: new Map() } } } },
schema,
Field.X,
"X",
{
summarize: {
method: "mean",
@@ -125,7 +122,7 @@ describe("whereCache", () => {
};
expect(
_whereCacheGet(whereCache, schema, Field.X, {
_whereCacheGet(whereCache, schema, "X", {
where: {
field: "var",
column: "foo",
@@ -134,7 +131,7 @@ describe("whereCache", () => {
})
).toEqual([0]);
expect(
_whereCacheGet(whereCache, schema, Field.X, {
_whereCacheGet(whereCache, schema, "X", {
summarize: {
method: "mean",
field: "var",
@@ -144,7 +141,7 @@ describe("whereCache", () => {
})
).toEqual([0]);
expect(
_whereCacheGet(whereCache, schema, Field.X, {
_whereCacheGet(whereCache, schema, "X", {
where: {
field: "var",
column: "foo",
@@ -153,7 +150,7 @@ describe("whereCache", () => {
})
).toEqual([1, 2]);
expect(
_whereCacheGet(whereCache, schema, Field.X, {
_whereCacheGet(whereCache, schema, "X", {
summarize: {
method: "mean",
field: "var",
@@ -162,12 +159,9 @@ describe("whereCache", () => {
},
})
).toEqual([1, 2]);
expect(_whereCacheGet(whereCache, schema, "Y", {})).toEqual([undefined]);
expect(
// Force invalid field value Y
_whereCacheGet(whereCache, schema, "Y" as Field, {} as Query)
).toEqual([undefined]);
expect(
_whereCacheGet(whereCache, schema, Field.X, {
_whereCacheGet(whereCache, schema, "X", {
where: {
field: "whoknows",
column: "whatever",
@@ -176,7 +170,7 @@ describe("whereCache", () => {
})
).toEqual([undefined]);
expect(
_whereCacheGet(whereCache, schema, Field.X, {
_whereCacheGet(whereCache, schema, "X", {
where: {
field: "var",
column: "whatever",
@@ -185,7 +179,7 @@ describe("whereCache", () => {
})
).toEqual([undefined]);
expect(
_whereCacheGet(whereCache, schema, Field.X, {
_whereCacheGet(whereCache, schema, "X", {
where: {
field: "var",
column: "foo",
@@ -204,7 +198,7 @@ describe("whereCache", () => {
},
};
const wc = _whereCacheCreate(
Field.obs,
"field",
{
where: {
field: "queryField",
@@ -218,27 +212,18 @@ describe("whereCache", () => {
expect(wc).toEqual(
expect.objectContaining({
where: {
[Field.obs]: {
field: {
queryField: expect.any(Map),
},
},
})
);
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
expect((wc.where as any)[Field.obs].queryField.has("queryColumn")).toEqual(true);
expect(wc.where.field.queryField.has("queryColumn")).toEqual(true);
expect(wc.where.field.queryField.get("queryColumn")).toBeInstanceOf(Map);
expect(
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(wc.where as any)[Field.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")
wc.where.field.queryField.get("queryColumn").has("queryValue")
).toEqual(true);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null
expect(_whereCacheGet(wc!, schema, Field.obs, query)).toEqual([0, 1, 2]);
expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
});
test("whereCacheCreate, summarize query", () => {
@@ -250,14 +235,12 @@ describe("whereCache", () => {
values: ["queryValue"],
},
};
const wc = _whereCacheCreate(Field.obs, 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.obs, query)).toEqual([0, 1, 2]);
const wc = _whereCacheCreate("field", query, [0, 1, 2]);
expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]);
});
test("whereCacheCreate, unknown query type", () => {
// @ts-expect-error --- force invalid query {foobar: true}
expect(_whereCacheCreate(Field.obs, { foobar: true }, [1])).toEqual({});
expect(_whereCacheCreate("field", { foobar: true }, [1])).toEqual({});
});
test("whereCacheMerge, where queries", () => {
@@ -265,20 +248,19 @@ describe("whereCache", () => {
// remember, will mutate dst
const src = _whereCacheCreate(
Field.obs,
"field",
{ where: { field: "queryField", column: "queryColumn", value: "foo" } },
["foo"]
);
const dst1 = _whereCacheCreate(
Field.obs,
"field",
{ where: { field: "queryField", column: "queryColumn", value: "bar" } },
["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(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
where: {
field: "queryField",
column: "queryColumn",
@@ -287,7 +269,7 @@ describe("whereCache", () => {
})
).toEqual(["foo"]);
expect(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
where: {
field: "queryField",
column: "queryColumn",
@@ -297,14 +279,13 @@ describe("whereCache", () => {
).toEqual(["dst1"]);
const dst2 = _whereCacheCreate(
Field.obs,
"field",
{ where: { field: "queryField", column: "queryColumn", value: "bar" } },
["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(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
where: {
field: "queryField",
column: "queryColumn",
@@ -313,7 +294,7 @@ describe("whereCache", () => {
})
).toEqual(["foo"]);
expect(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
where: {
field: "queryField",
column: "queryColumn",
@@ -322,20 +303,17 @@ describe("whereCache", () => {
})
).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);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert src to be non-null
wc = _whereCacheMerge({ where: { obs: { queryField: new Map() } } }, src!);
wc = _whereCacheMerge({ where: { field: { queryField: new Map() } } }, src);
expect(wc).toEqual(src);
});
test("whereCacheMerge, mixed queries", () => {
const wc = _whereCacheMerge(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null
_whereCacheCreate(
Field.obs,
"field",
{
where: {
field: "queryField",
@@ -344,10 +322,9 @@ describe("whereCache", () => {
},
},
["a"]
)!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null
),
_whereCacheCreate(
Field.obs,
"field",
{
summarize: {
method: "mean",
@@ -357,11 +334,11 @@ describe("whereCache", () => {
},
},
["b"]
)!
)
);
expect(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
where: {
field: "queryField",
column: "queryColumn",
@@ -371,7 +348,7 @@ describe("whereCache", () => {
).toEqual(["a"]);
expect(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
summarize: {
method: "mean",
field: "queryField",
@@ -382,7 +359,7 @@ describe("whereCache", () => {
).toEqual(["b"]);
expect(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
where: {
field: "queryField",
column: "queryColumn",
@@ -392,7 +369,7 @@ describe("whereCache", () => {
).toEqual([undefined]);
expect(
_whereCacheGet(wc, schema, Field.obs, {
_whereCacheGet(wc, schema, "field", {
summarize: {
method: "no-such-method",
field: "queryField",

View File

@@ -1,19 +1,16 @@
import cloneDeep from "lodash.clonedeep";
import { NumberArray } from "../../src/common/types/arraytypes";
import calcCentroid from "../../src/util/centroid";
import quantile from "../../src/util/quantile";
import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix";
import * as REST from "./stateManager/sampleResponses";
import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers";
import { normalizeWritableCategoricalSchema } from "../../src/annoMatrix/normalize";
import { Dataframe } from "../../src/util/dataframe";
describe("centroid", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let schema: any;
let obsAnnotations: Dataframe;
let obsLayout: Dataframe;
let schema;
let obsAnnotations;
let obsLayout;
beforeAll(() => {
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
const expectedResult = [
quantile([0.5], obsLayout.col("umap_0").asArray() as NumberArray)[0],
quantile([0.5], obsLayout.col("umap_1").asArray() as NumberArray)[0],
quantile([0.5], obsLayout.col("umap_0").asArray())[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: any) => {
centroidResult.forEach((coordinate) => {
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
const expectedResult = [
quantile([0.5], obsLayout.col("umap_0").asArray() as NumberArray)[0],
quantile([0.5], obsLayout.col("umap_1").asArray() as NumberArray)[0],
quantile([0.5], obsLayout.col("umap_0").asArray())[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: any) => {
centroidResult.forEach((coordinate) => {
expect(coordinate).toEqual(expectedResult);
});
});

View File

@@ -6,8 +6,7 @@ describe("dataframe constructor", () => {
expect(df).toBeDefined();
expect(df.dims).toEqual([0, 0]);
expect(df).toHaveLength(0);
expect(df.ihasCol(0)).toBeFalsy();
expect(() => df.icol(0)).toThrow(RangeError);
expect(df.icol(0)).not.toBeDefined();
});
test("create with default indices", () => {
@@ -24,13 +23,6 @@ describe("dataframe constructor", () => {
expect(df.at(2, 1)).toEqual(1);
expect(df.iat(0, 0)).toEqual(0);
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", () => {
@@ -129,9 +121,7 @@ describe("simple data access", () => {
expect(df.has(3, "foo")).toBeFalsy();
expect(df.has(-1, "numbers")).toBeFalsy();
expect(df.has(-1, -1)).toBeFalsy();
expect(
df.has(null as unknown as number, null as unknown as number)
).toBeFalsy();
expect(df.has(null, null)).toBeFalsy();
expect(df.has(0, "foo")).toBeFalsy();
expect(df.has(99, "numbers")).toBeFalsy();
expect(df.has(99, "foo")).toBeFalsy();
@@ -259,12 +249,11 @@ describe("dataframe subsetting", () => {
const df = sourceDf.subset(
null,
["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.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", () => {
@@ -843,7 +832,7 @@ describe("dataframe factories", () => {
});
describe("dataframe col", () => {
let df: Dataframe.Dataframe;
let df = null;
beforeEach(() => {
df = new Dataframe.Dataframe(
[2, 2],
@@ -860,8 +849,8 @@ describe("dataframe col", () => {
expect(df).toBeDefined();
expect(df.col("A")).toBe(df.icol(0));
expect(df.col("B")).toBe(df.icol(1));
expect(() => df.col("undefined")).toThrow(RangeError);
expect(() => df.icol("undefined" as unknown as number)).toThrow(RangeError);
expect(df.col("undefined")).toBeUndefined();
expect(df.icol("undefined")).toBeUndefined();
const colA = df.col("A");
expect(colA).toBeInstanceOf(Function);
@@ -915,13 +904,13 @@ describe("dataframe col", () => {
expect(df.col("A").indexOf(true)).toEqual(0);
expect(df.col("A").indexOf(false)).toEqual(1);
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("B").indexOf(1)).toEqual(0);
expect(df.col("B").indexOf(0)).toEqual(1);
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();
});
});
@@ -964,7 +953,7 @@ describe("label indexing", () => {
test("offsets", () => {
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", () => {
@@ -1042,7 +1031,7 @@ describe("label indexing", () => {
false,
])
.labels()
).toEqual([]);
).toEqual(new Int32Array([]));
expect(
idx
.isubsetMask([
@@ -1136,14 +1125,16 @@ describe("label indexing", () => {
expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22]));
expect(idx.size()).toEqual(5);
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]);
});
test("offsets", () => {
expect(idx.getOffset(1002)).toEqual(1);
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", () => {
@@ -1170,7 +1161,7 @@ describe("label indexing", () => {
);
expect(
idx.isubsetMask([false, false, false, false, false]).labels()
).toEqual([]);
).toEqual(new Int32Array([]));
expect(idx.isubsetMask([true, true, false, true, true]).labels()).toEqual(
new Int32Array([99, 1002, 0, 22])
);
@@ -1202,7 +1193,6 @@ describe("label indexing", () => {
test("create", () => {
expect(Dataframe.isLabelIndex(idx)).toBeTruthy();
expect(() => new Dataframe.KeyIndex(["dup", "dup"])).toThrow(Error);
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
expect(new Dataframe.KeyIndex().size()).toEqual(0);
});
@@ -1272,67 +1262,59 @@ describe("corner cases", () => {
const idx = new Dataframe.IdentityInt32Index(10);
expect(idx.getOffset(0)).toBe(0);
expect(idx.getOffset(9)).toBe(9);
expect(idx.getOffset(10)).toBe(-1);
expect(idx.getOffset(-1)).toBe(-1);
expect(idx.getOffset("sort")).toBe(-1);
expect(idx.getOffset("length")).toBe(-1);
expect(idx.getOffset(true as unknown as string)).toBe(-1);
expect(idx.getOffset(0.001)).toBe(-1);
expect(idx.getOffset({} as unknown as string)).toBe(-1);
expect(idx.getOffset([] as unknown as string)).toBe(-1);
expect(idx.getOffset(new Float32Array() as unknown as string)).toBe(-1);
expect(idx.getOffset("__proto__")).toBe(-1);
expect(idx.getOffset(10)).toBeUndefined();
expect(idx.getOffset(-1)).toBeUndefined();
expect(idx.getOffset("sort")).toBeUndefined();
expect(idx.getOffset("length")).toBeUndefined();
expect(idx.getOffset(true)).toBeUndefined();
expect(idx.getOffset(0.001)).toBeUndefined();
expect(idx.getOffset({})).toBeUndefined();
expect(idx.getOffset([])).toBeUndefined();
expect(idx.getOffset(new Float32Array())).toBeUndefined();
expect(idx.getOffset("__proto__")).toBeUndefined();
expect(idx.getLabel(0)).toBe(0);
expect(idx.getLabel(9)).toBe(9);
expect(idx.getLabel(10)).toBeUndefined();
expect(idx.getLabel(-1)).toBeUndefined();
expect(idx.getLabel("sort" as unknown as number)).toBeUndefined();
expect(idx.getLabel("length" as unknown as number)).toBeUndefined();
expect(idx.getLabel(true as unknown as number)).toBeUndefined();
expect(idx.getLabel("sort")).toBeUndefined();
expect(idx.getLabel("length")).toBeUndefined();
expect(idx.getLabel(true)).toBeUndefined();
expect(idx.getLabel(0.001)).toBeUndefined();
expect(idx.getLabel({} as unknown as number)).toBeUndefined();
expect(idx.getLabel([] as unknown as number)).toBeUndefined();
expect(
idx.getLabel(new Float32Array() as unknown as number)
).toBeUndefined();
expect(idx.getLabel("__proto__" as unknown as number)).toBeUndefined();
expect(idx.getLabel({})).toBeUndefined();
expect(idx.getLabel([])).toBeUndefined();
expect(idx.getLabel(new Float32Array())).toBeUndefined();
expect(idx.getLabel("__proto__")).toBeUndefined();
});
test("dense integer index rejects non-integer labels", () => {
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(3)).toBe(2);
expect(idx.getOffset(9)).toBe(3);
expect(idx.getOffset(10)).toBe(4);
expect(idx.getOffset(1)).toBe(-1);
expect(idx.getOffset(11)).toBe(-1);
expect(idx.getOffset(-1)).toBe(-1);
expect(idx.getOffset("sort")).toBe(-1);
expect(idx.getOffset("length")).toBe(-1);
expect(idx.getOffset(true as unknown as string)).toBe(-1);
expect(idx.getOffset(0.001)).toBe(-1);
expect(idx.getOffset({} as unknown as string)).toBe(-1);
expect(idx.getOffset([] as unknown as string)).toBe(-1);
expect(idx.getOffset(new Float32Array() as unknown as string)).toBe(-1);
expect(idx.getOffset("__proto__")).toBe(-1);
expect(idx.getOffset(1)).toBeUndefined();
expect(idx.getOffset(11)).toBeUndefined();
expect(idx.getOffset(-1)).toBeUndefined();
expect(idx.getOffset("sort")).toBeUndefined();
expect(idx.getOffset("length")).toBeUndefined();
expect(idx.getOffset(true)).toBeUndefined();
expect(idx.getOffset(0.001)).toBeUndefined();
expect(idx.getOffset({})).toBeUndefined();
expect(idx.getOffset([])).toBeUndefined();
expect(idx.getOffset(new Float32Array())).toBeUndefined();
expect(idx.getOffset("__proto__")).toBeUndefined();
expect(idx.getLabel(0)).toBe(-10);
expect(idx.getLabel(4)).toBe(10);
expect(idx.getLabel(10)).toBeUndefined();
expect(idx.getLabel(-1)).toBeUndefined();
expect(idx.getLabel("sort" as unknown as number)).toBeUndefined();
expect(idx.getLabel("length" as unknown as number)).toBeUndefined();
expect(idx.getLabel(true as unknown as number)).toBeUndefined();
expect(idx.getLabel("sort")).toBeUndefined();
expect(idx.getLabel("length")).toBeUndefined();
expect(idx.getLabel(true)).toBeUndefined();
expect(idx.getLabel(0.001)).toBeUndefined();
expect(idx.getLabel({} as unknown as number)).toBeUndefined();
expect(idx.getLabel([] as unknown as number)).toBeUndefined();
expect(
idx.getLabel(new Float32Array() as unknown as number)
).toBeUndefined();
expect(idx.getLabel("__proto__" as unknown as number)).toBeUndefined();
expect(idx.getLabel({})).toBeUndefined();
expect(idx.getLabel([])).toBeUndefined();
expect(idx.getLabel(new Float32Array())).toBeUndefined();
expect(idx.getLabel("__proto__")).toBeUndefined();
});
test("Empty dataframe rejects bogus labels", () => {
@@ -1340,55 +1322,39 @@ describe("corner cases", () => {
expect(df.hasCol("sort")).toBeFalsy();
expect(df.hasCol(0)).toBeFalsy();
expect(df.hasCol(true as unknown as number)).toBeFalsy();
expect(df.hasCol(false as unknown as number)).toBeFalsy();
expect(df.hasCol([] as unknown as number)).toBeFalsy();
expect(df.hasCol({} as unknown as number)).toBeFalsy();
expect(df.hasCol(null as unknown as number)).toBeFalsy();
expect(df.hasCol(undefined as unknown as number)).toBeFalsy();
expect(df.hasCol(true)).toBeFalsy();
expect(df.hasCol(false)).toBeFalsy();
expect(df.hasCol([])).toBeFalsy();
expect(df.hasCol({})).toBeFalsy();
expect(df.hasCol(null)).toBeFalsy();
expect(df.hasCol(undefined)).toBeFalsy();
expect(() => df.col("sort")).toThrow(RangeError);
expect(() => df.col(0)).toThrow(RangeError);
expect(() => df.col(true as unknown as string)).toThrow(RangeError);
expect(() => df.col(false as unknown as string)).toThrow(RangeError);
expect(() => df.col([] as unknown as string)).toThrow(RangeError);
expect(() => df.col({} as unknown as string)).toThrow(RangeError);
expect(() => df.col(null as unknown as string)).toThrow(RangeError);
expect(() => df.col(undefined as unknown as string)).toThrow(RangeError);
expect(df.col("sort")).toBeUndefined();
expect(df.col(0)).toBeUndefined();
expect(df.col(true)).toBeUndefined();
expect(df.col(false)).toBeUndefined();
expect(df.col([])).toBeUndefined();
expect(df.col({})).toBeUndefined();
expect(df.col(null)).toBeUndefined();
expect(df.col(undefined)).toBeUndefined();
expect(() => df.icol("sort" as unknown as number)).toThrow(RangeError);
expect(() => df.icol(0)).toThrow(RangeError);
expect(() => df.icol(true as unknown as number)).toThrow(RangeError);
expect(() => df.icol(false as unknown as number)).toThrow(RangeError);
expect(() => df.icol([] as unknown as number)).toThrow(RangeError);
expect(() => df.icol({} as unknown as number)).toThrow(RangeError);
expect(() => df.icol(null as unknown as number)).toThrow(RangeError);
expect(() => df.icol(undefined as unknown as number)).toThrow(RangeError);
expect(df.icol("sort")).toBeUndefined();
expect(df.icol(0)).toBeUndefined();
expect(df.icol(true)).toBeUndefined();
expect(df.icol(false)).toBeUndefined();
expect(df.icol([])).toBeUndefined();
expect(df.icol({})).toBeUndefined();
expect(df.icol(null)).toBeUndefined();
expect(df.icol(undefined)).toBeUndefined();
expect(
df.ihas("sort" as unknown as number, "length" as unknown as number)
).toBeFalsy();
expect(
df.ihas("0" as unknown as number, "0" as unknown as number)
).toBeFalsy();
expect(
df.ihas("" as unknown as number, "" as unknown as number)
).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();
expect(df.ihas("sort", "length")).toBeFalsy();
expect(df.ihas("0", "0")).toBeFalsy();
expect(df.ihas("", "")).toBeFalsy();
expect(df.ihas(null, null)).toBeFalsy();
expect(df.ihas(undefined, undefined)).toBeFalsy();
expect(df.ihas(true, true)).toBeFalsy();
expect(df.ihas([], [])).toBeFalsy();
expect(df.ihas({}, {})).toBeFalsy();
});
test("Dataframe rejects bogus labels", () => {
@@ -1405,58 +1371,51 @@ describe("corner cases", () => {
expect(df.hasCol("sort")).toBeFalsy();
expect(df.hasCol("__proto__")).toBeFalsy();
expect(df.hasCol(0)).toBeFalsy();
expect(df.hasCol(true as unknown as string)).toBeFalsy();
expect(df.hasCol(false as unknown as string)).toBeFalsy();
expect(df.hasCol([] as unknown as string)).toBeFalsy();
expect(df.hasCol({} as unknown as string)).toBeFalsy();
expect(df.hasCol(null as unknown as string)).toBeFalsy();
expect(df.hasCol(undefined as unknown as string)).toBeFalsy();
expect(df.hasCol(true)).toBeFalsy();
expect(df.hasCol(false)).toBeFalsy();
expect(df.hasCol([])).toBeFalsy();
expect(df.hasCol({})).toBeFalsy();
expect(df.hasCol(null)).toBeFalsy();
expect(df.hasCol(undefined)).toBeFalsy();
expect(() => df.col("sort")).toThrow(RangeError);
expect(() => df.col("__proto__")).toThrow(RangeError);
expect(() => df.col(0)).toThrow(RangeError);
expect(() => df.col(true as unknown as string)).toThrow(RangeError);
expect(() => df.col(false as unknown as string)).toThrow(RangeError);
expect(() => df.col([] as unknown as string)).toThrow(RangeError);
expect(() => df.col({} as unknown as string)).toThrow(RangeError);
expect(() => df.col(null as unknown as string)).toThrow(RangeError);
expect(() => df.col(undefined as unknown as string)).toThrow(RangeError);
expect(df.col("sort")).toBeUndefined();
expect(df.col("__proto__")).toBeUndefined();
expect(df.col(0)).toBeUndefined();
expect(df.col(true)).toBeUndefined();
expect(df.col(false)).toBeUndefined();
expect(df.col([])).toBeUndefined();
expect(df.col({})).toBeUndefined();
expect(df.col(null)).toBeUndefined();
expect(df.col(undefined)).toBeUndefined();
expect(() => df.icol("sort" as unknown as number)).toThrow(RangeError);
expect(() => df.icol("__proto__" as unknown as number)).toThrow(RangeError);
expect(() => df.icol(-1)).toThrow(RangeError);
expect(() => df.icol(true as unknown as number)).toThrow(RangeError);
expect(() => df.icol(false as unknown as number)).toThrow(RangeError);
expect(() => df.icol([] as unknown as number)).toThrow(RangeError);
expect(() => df.icol({} as unknown as number)).toThrow(RangeError);
expect(() => df.icol(null as unknown as number)).toThrow(RangeError);
expect(() => df.icol(undefined as unknown as number)).toThrow(RangeError);
expect(df.icol("sort")).toBeUndefined();
expect(df.icol("__proto__")).toBeUndefined();
expect(df.icol(-1)).toBeUndefined();
expect(df.icol(true)).toBeUndefined();
expect(df.icol(false)).toBeUndefined();
expect(df.icol([])).toBeUndefined();
expect(df.icol({})).toBeUndefined();
expect(df.icol(null)).toBeUndefined();
expect(df.icol(undefined)).toBeUndefined();
expect(
df.ihas("sort" as unknown as number, "length" as unknown as number)
).toBeFalsy();
expect(
df.ihas(
"__proto__" as unknown as number,
"__proto__" as unknown as number
)
).toBeFalsy();
expect(df.ihas("sort", "length")).toBeFalsy();
expect(df.ihas("__proto__", "__proto__")).toBeFalsy();
expect(df.ihas(-1, 0)).toBeFalsy();
expect(df.ihas("0" as unknown as number, 0)).toBeFalsy();
expect(df.ihas("" as unknown as number, 0)).toBeFalsy();
expect(df.ihas(null as unknown as number, 0)).toBeFalsy();
expect(df.ihas(undefined as unknown as number, 0)).toBeFalsy();
expect(df.ihas([] as unknown as number, 0)).toBeFalsy();
expect(df.ihas({} as unknown as number, 0)).toBeFalsy();
expect(df.ihas("0", 0)).toBeFalsy();
expect(df.ihas("", 0)).toBeFalsy();
expect(df.ihas(null, 0)).toBeFalsy();
expect(df.ihas(undefined, 0)).toBeFalsy();
expect(df.ihas([], 0)).toBeFalsy();
expect(df.ihas({}, 0)).toBeFalsy();
expect(df.ihas(0, -1)).toBeFalsy();
expect(df.ihas(0, "0" as unknown as number)).toBeFalsy();
expect(df.ihas(0, "" as unknown as number)).toBeFalsy();
expect(df.ihas(0, null as unknown as number)).toBeFalsy();
expect(df.ihas(0, undefined as unknown as number)).toBeFalsy();
expect(df.ihas(0, [] as unknown as number)).toBeFalsy();
expect(df.ihas(0, {} as unknown as number)).toBeFalsy();
expect(df.ihas(0, "0")).toBeFalsy();
expect(df.ihas(0, "")).toBeFalsy();
expect(df.ihas(0, null)).toBeFalsy();
expect(df.ihas(0, undefined)).toBeFalsy();
expect(df.ihas(0, [])).toBeFalsy();
expect(df.ihas(0, {})).toBeFalsy();
expect(df.has("sort", "length")).toBeFalsy();
expect(df.has("length", "sort")).toBeFalsy();
@@ -1465,17 +1424,17 @@ describe("corner cases", () => {
expect(df.has(-1, "A")).toBeFalsy();
expect(df.has("0", "A")).toBeFalsy();
expect(df.has("", "A")).toBeFalsy();
expect(df.has(null as unknown as string, "A")).toBeFalsy();
expect(df.has(undefined as unknown as string, "A")).toBeFalsy();
expect(df.has([] as unknown as string, "A")).toBeFalsy();
expect(df.has({} as unknown as string, "A")).toBeFalsy();
expect(df.has(null, "A")).toBeFalsy();
expect(df.has(undefined, "A")).toBeFalsy();
expect(df.has([], "A")).toBeFalsy();
expect(df.has({}, "A")).toBeFalsy();
expect(df.has(0, -1)).toBeFalsy();
expect(df.has(0, "0")).toBeFalsy();
expect(df.has(0, "")).toBeFalsy();
expect(df.has(0, null as unknown as string)).toBeFalsy();
expect(df.has(0, undefined as unknown as string)).toBeFalsy();
expect(df.has(0, [] as unknown as string)).toBeFalsy();
expect(df.has(0, {} as unknown as string)).toBeFalsy();
expect(df.has(0, null)).toBeFalsy();
expect(df.has(0, undefined)).toBeFalsy();
expect(df.has(0, [])).toBeFalsy();
expect(df.has(0, {})).toBeFalsy();
});
});

View File

@@ -9,7 +9,7 @@ describe("Dataframe column histogram", () => {
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(
new Map([
["n1", new Map([["c1", 1]])],
@@ -18,9 +18,7 @@ describe("Dataframe column histogram", () => {
])
);
// memoized?
expect(df.col("cat").histogramCategoricalBy(df.col("name"))).toMatchObject(
h1
);
expect(df.col("cat").histogram(df.col("name"))).toMatchObject(h1);
});
test("continuous by categorical", () => {
@@ -31,7 +29,7 @@ describe("Dataframe column histogram", () => {
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(
new Map([
["n1", [1, 0, 0]],
@@ -40,9 +38,9 @@ describe("Dataframe column histogram", () => {
])
);
// memoized?
expect(
df.col("value").histogramContinuousBy(3, [0, 2], df.col("name"))
).toMatchObject(h1);
expect(df.col("value").histogram(3, [0, 2], df.col("name"))).toMatchObject(
h1
);
});
test("categorical", () => {
@@ -53,7 +51,7 @@ describe("Dataframe column histogram", () => {
new Dataframe.KeyIndex(["name", "cat", "value"])
);
const h1 = df.col("cat").histogramCategorical();
const h1 = df.col("cat").histogram();
expect(h1).toMatchObject(
new Map([
["c1", 1],
@@ -62,7 +60,7 @@ describe("Dataframe column histogram", () => {
])
);
// memoized?
expect(df.col("cat").histogramCategorical()).toMatchObject(h1);
expect(df.col("value").histogram(3, [0, 2])).toMatchObject(h1);
});
test("continuous", () => {
@@ -73,10 +71,10 @@ describe("Dataframe column histogram", () => {
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]);
// 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", () => {
@@ -86,10 +84,10 @@ describe("Dataframe column histogram", () => {
[new Int32Array(vals), new Float32Array(vals)]
);
expect(df.col(0).histogramContinuous(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(0).histogramContinuous(2, [0, 10])).toEqual([2, 2]);
expect(df.col(0).histogramContinuous(10, [0, 100])).toEqual([
expect(df.col(0).histogram(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).histogram(2, [0, 10])).toEqual([2, 2]);
expect(df.col(0).histogram(10, [0, 100])).toEqual([
3, 2, 1, 0, 0, 0, 0, 0, 0, 2,
]);
});

View File

@@ -1,14 +1,13 @@
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: any) {
function float32Conversion(f) {
return new Float32Array([f])[0];
}
describe("Dataframe column summary", () => {
test("empty column test", () => {
const df = Dataframe.Dataframe.create([0, 1], [[]]);
const summary = df.icol(0).summarizeCategorical();
const summary = df.icol(0).summarize();
expect(summary).toEqual(
expect.objectContaining({
categorical: true,
@@ -41,7 +40,7 @@ describe("Dataframe column summary", () => {
])
);
expect(df.icol(0).summarizeCategorical()).toEqual(
expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: ["n1"],
@@ -49,7 +48,7 @@ describe("Dataframe column summary", () => {
numCategories: 1,
})
);
expect(df.icol(1).summarizeCategorical()).toEqual(
expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: ["hi"],
@@ -57,7 +56,7 @@ describe("Dataframe column summary", () => {
numCategories: 1,
})
);
expect(df.icol(2).summarizeCategorical()).toEqual(
expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: [true],
@@ -65,7 +64,7 @@ describe("Dataframe column summary", () => {
numCategories: 1,
})
);
expect(df.icol(3).summarizeContinuous()).toEqual(
expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: float32Conversion(39.3),
@@ -75,7 +74,7 @@ describe("Dataframe column summary", () => {
pinf: 0,
})
);
expect(df.icol(4).summarizeContinuous()).toEqual(
expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 99,
@@ -85,7 +84,7 @@ describe("Dataframe column summary", () => {
pinf: 0,
})
);
expect(df.icol(5).summarizeCategorical()).toEqual(
expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: [1],
@@ -117,7 +116,7 @@ describe("Dataframe column summary", () => {
])
);
expect(df.icol(0).summarizeCategorical()).toEqual(
expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]),
@@ -129,7 +128,7 @@ describe("Dataframe column summary", () => {
numCategories: 3,
})
);
expect(df.icol(1).summarizeCategorical()).toEqual(
expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["hi", "bye"]),
@@ -140,7 +139,7 @@ describe("Dataframe column summary", () => {
numCategories: 2,
})
);
expect(df.icol(2).summarizeCategorical()).toEqual(
expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([true, false]),
@@ -151,7 +150,7 @@ describe("Dataframe column summary", () => {
numCategories: 2,
})
);
expect(df.icol(3).summarizeContinuous()).toEqual(
expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 0,
@@ -161,7 +160,7 @@ describe("Dataframe column summary", () => {
pinf: 0,
})
);
expect(df.icol(4).summarizeContinuous()).toEqual(
expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 99,
@@ -171,11 +170,10 @@ describe("Dataframe column summary", () => {
pinf: 0,
})
);
expect(df.icol(5).summarizeCategorical()).toEqual(
expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
categoryCounts: new Map([
[1, 1],
[false, 1],
@@ -213,7 +211,7 @@ describe("Dataframe column summary", () => {
])
);
expect(df.icol(0).summarizeCategorical()).toEqual(
expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]),
@@ -225,7 +223,7 @@ describe("Dataframe column summary", () => {
numCategories: 3,
})
);
expect(df.icol(1).summarizeCategorical()).toEqual(
expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["hi", "bye"]),
@@ -236,7 +234,7 @@ describe("Dataframe column summary", () => {
numCategories: 2,
})
);
expect(df.icol(2).summarizeCategorical()).toEqual(
expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([true, false]),
@@ -247,7 +245,7 @@ describe("Dataframe column summary", () => {
numCategories: 2,
})
);
expect(df.icol(3).summarizeContinuous()).toEqual(
expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: float32Conversion(39.3),
@@ -257,7 +255,7 @@ describe("Dataframe column summary", () => {
pinf: 1,
})
);
expect(df.icol(4).summarizeContinuous()).toEqual(
expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 99,
@@ -267,11 +265,10 @@ describe("Dataframe column summary", () => {
pinf: 0,
})
);
expect(df.icol(5).summarizeCategorical()).toEqual(
expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
categoryCounts: new Map([
[1, 1],
[false, 1],

View File

@@ -1,8 +1,7 @@
import PromiseLimit from "../../src/util/promiseLimit";
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: any) => new Promise((resolve) => setTimeout(resolve, t));
const delay = (t) => new Promise((resolve) => setTimeout(resolve, t));
describe("PromiseLimit", () => {
test("simple evaluation, concurrency 1", async () => {
@@ -52,7 +51,7 @@ describe("PromiseLimit", () => {
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);
});

View File

@@ -19,11 +19,7 @@ describe("quantile", () => {
test("multi q", () => {
const arr = new Float32Array([9, 3, 5, 6, 0]);
expect(quantile([0, 0.25, 0.5, 0.75, 1.0], arr)).toMatchObject([
0,
3,
5,
6,
9,
0, 3, 5, 6, 9,
]);
});
});

View File

@@ -95,7 +95,6 @@ describe("categorical color helpers", () => {
const data = obsDataframe.col("categoricalColumn").asArray();
const cats = schema.annotations.obsByName.categoricalColumn.categories;
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
}
});
@@ -113,7 +112,6 @@ describe("categorical color helpers", () => {
const data = obsDataframe.col("categoricalColumn").asArray();
const cats = schemaClone.annotations.obsByName.categoricalColumn.categories;
for (let i = 0; i < schemaClone.dataframe.nObs; i += 1) {
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
expect(makeScale(ct.rgb[i])).toEqual(ct.scale(cats.indexOf(data[i])));
}
});
@@ -124,8 +122,7 @@ describe("categorical color helpers", () => {
Array.from(schema.annotations.obsByName.categoricalColumn.categories)
);
const userDefinedColorTable = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoricalColumn: shuffleCats.reduce((acc: any, label: any) => {
categoricalColumn: shuffleCats.reduce((acc, label) => {
acc[label] = randRGBColor();
return acc;
}, {}),
@@ -139,14 +136,12 @@ describe("categorical color helpers", () => {
"categoricalColumn",
obsDataframe,
schema,
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{}' is not assignable to paramet... Remove this comment to see the full error message
userColors
);
expect(ct).toBeDefined();
const data = obsDataframe.col("categoricalColumn").asArray();
for (let i = 0; i < schema.dataframe.nObs; i += 1) {
expect(makeScale(ct.rgb[i])).toEqual(
// @ts-expect-error ts-migrate(2722) FIXME: Cannot invoke an object which is possibly 'undefin... Remove this comment to see the full error message
ct.scale(cats.indexOf(data[i])).toString()
);
}
@@ -159,38 +154,31 @@ TODO:
2. user defined colors
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function indexSchema(schema: any) {
function indexSchema(schema) {
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: any) => [v.name, v]) ?? []
schema.annotations?.obs?.columns?.map((v) => [v.name, v]) ?? []
);
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: any) => [v.name, v]) ?? []
schema.annotations?.var?.columns?.map((v) => [v.name, v]) ?? []
);
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: any) => [v.name, v]) ?? []
schema.layout?.obs?.map((v) => [v.name, v]) ?? []
);
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: any) => [v.name, v]) ?? []
schema.layout?.var?.map((v) => [v.name, v]) ?? []
);
return schema;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function makeScale(rgb: any) {
function makeScale(rgb) {
// make a scale string from a rgb float triple
return `rgb(${(rgb[0] * 255) >>> 0}, ${(rgb[1] * 255) >>> 0}, ${
(rgb[2] * 256) >>> 0
})`;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function shuffle(array: any) {
function shuffle(array) {
for (let i = array.length - 1; i > 0; i -= 1) {
const j = (Math.random() * (i + 1)) >>> 0;
[array[i], array[j]] = [array[j], array[i]];

View File

@@ -5,7 +5,6 @@ import zip from "lodash.zip";
import _ from "lodash";
import { flatbuffers } from "flatbuffers";
import { NetEncoding } from "../../../src/util/stateManager/matrix_generated";
import { RawSchema } from "../../../src/common/types/schema";
/*
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: {
dataframe: {
nObs,
@@ -41,30 +40,28 @@ const aSchemaResponse: { schema: RawSchema } = {
obs: {
index: "name",
columns: [
{ name: "name", type: "string", writable: false },
{ name: "field1", type: "int32", writable: false },
{ name: "field2", type: "float32", writable: false },
{ name: "field3", type: "boolean", writable: false },
{ name: "name", type: "string" },
{ name: "field1", type: "int32" },
{ name: "field2", type: "float32" },
{ name: "field3", type: "boolean" },
{
name: "field4",
type: "categorical",
categories: field4Categories,
writable: false,
},
],
},
var: {
index: "name",
columns: [
{ name: "name", type: "string", writable: false },
{ name: "fieldA", type: "int32", writable: false },
{ name: "fieldB", type: "float32", writable: false },
{ name: "fieldC", type: "boolean", writable: false },
{ name: "name", type: "string" },
{ name: "fieldA", type: "int32" },
{ name: "fieldB", type: "float32" },
{ name: "fieldC", type: "boolean" },
{
name: "fieldD",
type: "categorical",
categories: fieldDCategories,
writable: false,
},
],
},
@@ -78,7 +75,6 @@ const aSchemaResponse: { schema: RawSchema } = {
const anAnnotationsObsJSONResponse = {
names: ["name", "field1", "field2", "field3", "field4"],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
data: _()
.range(nObs)
.map((idx) => [
@@ -95,7 +91,6 @@ const anAnnotationsObsJSONResponse = {
const anAnnotationsVarJSONResponse = {
names: ["fieldA", "fieldB", "fieldC", "fieldD", "name"],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
data: _()
.range(nVar)
.map((idx) => [
@@ -110,11 +105,8 @@ const anAnnotationsVarJSONResponse = {
.value(),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function encodeTypedArray(builder: any, uType: any, uData: any) {
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
function encodeTypedArray(builder, uType, uData) {
const uTypeName = NetEncoding.TypedArray[uType];
// @ts-expect-error --- FIXME: Element implicitly has an 'any' type.
const ArrayType = NetEncoding[uTypeName];
const dv = ArrayType.createDataVector(builder, uData);
builder.startObject(1);
@@ -122,8 +114,7 @@ function encodeTypedArray(builder: any, uType: any, uData: any) {
return builder.endObject();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function encodeMatrix(columns: any, colIndex = undefined) {
function encodeMatrix(columns, colIndex = undefined) {
/*
IMPORTANT: this is not a general purpose encoder. in particular,
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
as a testing santity check (alt implementation).
*/
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
const utf8Encoder = new TextEncoder("utf-8");
const builder = new flatbuffers.Builder(1024);
const cols = map(columns, (carr) => {
@@ -182,13 +172,11 @@ function encodeMatrix(columns: any, colIndex = undefined) {
const anAnnotationsObsFBSResponse = (() => {
const columns = zip(...anAnnotationsObsJSONResponse.data).slice(1);
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
return encodeMatrix(columns, anAnnotationsObsJSONResponse.names);
})();
const anAnnotationsVarFBSResponse = (() => {
const columns = zip(...anAnnotationsVarJSONResponse.data).slice(1);
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
return encodeMatrix(columns, anAnnotationsVarJSONResponse.names);
})();
@@ -197,13 +185,11 @@ const aLayoutFBSResponse = (() => {
new Float32Array(nObs).fill(Math.random()),
new Float32Array(nObs).fill(Math.random()),
];
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string[]' is not assignable to p... Remove this comment to see the full error message
return encodeMatrix(coords, ["umap_0", "umap_1"]);
})();
const aDataObsResponse = {
var: [2, 4, 29],
// @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 0.
obs: _()
.range(nObs)
.map((idx) => [idx, Math.random(), Math.random(), Math.random()])

View File

@@ -126,8 +126,7 @@ const someData = [
},
];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let payments: any = null;
let payments = null;
beforeEach(() => {
payments = new Crossfilter(someData);
});
@@ -139,13 +138,7 @@ describe("ImmutableTypedCrossfilter", () => {
expect(payments.all()).toEqual(someData);
const p = payments
.addDimension(
"quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
)
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
.select("quantity", { mode: "all" });
expect(p).toBeDefined();
expect(p.all()).toEqual(someData);
@@ -165,8 +158,7 @@ describe("ImmutableTypedCrossfilter", () => {
const p2 = payments.addDimension(
"quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, data: any) => data[i].quantity,
(i, data) => data[i].quantity,
Int32Array
);
@@ -183,22 +175,10 @@ describe("ImmutableTypedCrossfilter", () => {
test("select all and none", () => {
let p = payments
.addDimension(
"quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.addDimension("tip", "scalar", (i: any, d: any) => d[i].tip, Float32Array)
.addDimension(
"total",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].total,
Float32Array
) // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.addDimension("type", "enum", (i: any, d: any) => d[i].type);
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
.addDimension("total", "scalar", (i, d) => d[i].total, Float32Array)
.addDimension("type", "enum", (i, d) => d[i].type);
expect(p).toBeDefined();
/* expect all records to be selected - default init state */
@@ -250,24 +230,11 @@ describe("ImmutableTypedCrossfilter", () => {
});
describe("scalar dimension", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
let p;
beforeEach(() => {
p = payments
.addDimension(
"quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
)
.addDimension(
"tip",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].tip,
Float32Array
)
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
.addDimension("tip", "scalar", (i, d) => d[i].tip, Float32Array)
.select("tip", { mode: "all" });
});
@@ -310,11 +277,9 @@ describe("ImmutableTypedCrossfilter", () => {
});
describe("enum dimension", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
let p;
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
p = payments.addDimension("type", "enum", (i: any, d: any) => d[i].type);
p = payments.addDimension("type", "enum", (i, d) => d[i].type);
});
test("all", () => {
@@ -352,8 +317,7 @@ describe("ImmutableTypedCrossfilter", () => {
});
describe("spatial dimension", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
let p;
beforeEach(() => {
const X = someData.map((r) => r.coords[0]);
const Y = someData.map((r) => r.coords[1]);
@@ -442,22 +406,14 @@ describe("ImmutableTypedCrossfilter", () => {
});
describe("non-finite scalars", () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let p: any;
let p;
beforeEach(() => {
p = payments
.addDimension(
"quantity",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].quantity,
Int32Array
)
.addDimension("quantity", "scalar", (i, d) => d[i].quantity, Int32Array)
.addDimension(
"nonFinite",
"scalar",
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(i: any, d: any) => d[i].nonFinite,
(i, d) => d[i].nonFinite,
Float32Array
)
.select("quantity", { mode: "all" });

View File

@@ -151,9 +151,9 @@ describe("intersection", () => {
[1, 2],
[6, 9],
]);
expect(
PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])
).toEqual([[1363, 2638]]);
expect(PositiveIntervals.intersection([[0, 2638]], [[1363, 2638]])).toEqual(
[[1363, 2638]]
);
expect(PositiveIntervals.intersection([[1, 2]], [[1, 2]])).toEqual([
[1, 2],
]);

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,8 @@
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
parser: "babel-eslint",
extends: [
"airbnb-typescript",
"plugin:@typescript-eslint/recommended",
"airbnb",
"plugin:eslint-comments/recommended",
"plugin:@blueprintjs/recommended",
"plugin:compat/recommended",
@@ -37,50 +35,18 @@ module.exports = {
jsx: true,
generators: true,
},
// (thuang): Pairing with `tsconfigRootDir`, which points to the directory
// of eslint.js
project: "../../tsconfig.json",
tsconfigRootDir: __dirname,
},
rules: {
"react/jsx-no-target-blank": "off",
"eslint-comments/require-description": ["error"],
"no-magic-numbers": "off",
"@typescript-eslint/no-magic-numbers": "off",
"no-nested-ternary": "off",
"func-style": "off",
"arrow-parens": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"react/jsx-filename-extension": "off",
"comma-dangle": "off",
"@typescript-eslint/comma-dangle": "off",
"no-underscore-dangle": "off",
// Override airbnb config to allow leading underscore
// https://github.com/iamturns/eslint-config-airbnb-typescript/blob/master/lib/shared.js#L35
"@typescript-eslint/naming-convention": [
"error",
{
selector: "class",
format: ["PascalCase"],
leadingUnderscore: "allow",
},
{
selector: "function",
format: ["camelCase", "PascalCase"],
leadingUnderscore: "allowSingleOrDouble",
},
{
selector: "typeLike",
format: ["PascalCase"],
},
{
selector: "variable",
format: ["camelCase", "PascalCase", "UPPER_CASE"],
leadingUnderscore: "allowSingleOrDouble",
trailingUnderscore: "allowDouble",
},
],
"implicit-arrow-linebreak": "off",
"no-console": "off",
"spaced-comment": ["error", "always", { exceptions: ["*"] }],
@@ -88,7 +54,6 @@ module.exports = {
"object-curly-newline": ["error", { consistent: true }],
"react/prop-types": [0],
"space-before-function-paren": "off",
"@typescript-eslint/space-before-function-paren": "off",
"function-paren-newline": "off",
"prefer-destructuring": ["error", { object: true, array: false }],
"import/prefer-default-export": "off",
@@ -107,9 +72,9 @@ module.exports = {
},
overrides: [
{
files: ["**/*.test.ts"],
files: ["**/*.test.js"],
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
// "extends": ["plugin:jest/recommended"]
@@ -124,4 +89,3 @@ module.exports = {
},
],
};
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */

View File

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

View File

@@ -1,10 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies -- this file is a devDependency*/
/* eslint-disable @blueprintjs/classes-constants -- we don't import blueprint here */
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const cheerio = require("cheerio");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const crypto = require("crypto");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const HtmlWebpackPlugin = require("html-webpack-plugin");
const digest = (str) => {
@@ -54,5 +50,4 @@ class CspHashPlugin {
}
module.exports = CspHashPlugin;
/* eslint-enable @blueprintjs/classes-constants -- we don't import blueprint here */
/* eslint-enable import/no-extraneous-dependencies -- enable*/

View File

@@ -22,7 +22,7 @@
>
<img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/cellxgene-logo.png"
style="width: 320px"
style="width: 320px;"
/>
<div
style="
@@ -37,34 +37,36 @@
max-width: 550px;
"
>
<div style="margin-bottom: 0; font-weight: bolder; font-size: 1.2em">
<div style="margin-bottom: 0; font-weight: bolder; font-size: 1.2em;">
Unsupported Browser
</div>
<div style="margin-top: 0">
<div style="margin-top: 0;">
cellxgene is currently supported on the following browsers
</div>
<div style="display: flex; justify-content: space-around; margin-top: 16px">
<div
style="display: flex; justify-content: space-around; margin-top: 16px;"
>
<a
href="https://www.google.com/chrome/?hl=en%22"
aria-label="Download Google Chrome"
>
<img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/chrome.png"
style="width: 80px; height: 80px"
style="width: 80px; height: 80px;"
/>
<div>Chrome &gt; 60</div>
</a>
<a href="https://www.mozilla.com/firefox/" aria-label="Download Firefox">
<img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/firefox.png"
style="width: 80px; height: 80px"
style="width: 80px; height: 80px;"
/>
<div>Firefox ≥ 60</div>
</a>
<a href="//www.microsoft.com/edge" aria-label="Download Edge">
<img
src="https://cellxgene.cziscience.com/s3/cellxgene/static/images/edge.png"
style="width: 80px; height: 80px"
style="width: 80px; height: 80px;"
/>
<div>Edge ≥ 79</div>
</a>

View File

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

View File

@@ -1,31 +1,18 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const path = require("path");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const webpack = require("webpack");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const HtmlWebpackPlugin = require("html-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const TerserJSPlugin = require("terser-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const CleanCss = require("clean-css");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const { merge } = require("webpack-merge");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const babelOptions = require("../babel/babel.prod");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const CspHashPlugin = require("./cspHashPlugin");
// eslint-disable-next-line @typescript-eslint/no-var-requires --- FIXME: disabled temporarily on migrate to TS.
const sharedConfig = require("./webpack.config.shared");
const fonts = path.resolve("src/fonts");
@@ -51,7 +38,7 @@ const prodConfig = {
module: {
rules: [
{
test: /\.(ts|js)x?$/,
test: /\.jsx?$/,
loader: "babel-loader",
options: babelOptions,
},

View File

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

3934
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,14 +8,13 @@
"build": "npm run clean && webpack --config",
"clean": "rimraf build",
"dev": "npm run build -- configuration/webpack/webpack.config.dev.js",
"e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts",
"e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.ts",
"e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts",
"e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js",
"e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.js",
"e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js",
"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",
"test": "jest --testPathIgnorePatterns e2e",
"type-check": "tsc --noEmit"
"test": "jest --testPathIgnorePatterns e2e"
},
"engineStrict": true,
"engines": {
@@ -89,38 +88,10 @@
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "^7.13.15",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.14.5",
"@babel/register": "^7.13.16",
"@babel/runtime": "^7.13.16",
"@blueprintjs/eslint-plugin": "^0.3.0",
"@sentry/webpack-plugin": "^1.15.0",
"@types/d3": "^7.0.0",
"@types/d3-scale-chromatic": "^3.0.0",
"@types/expect-puppeteer": "^4.4.6",
"@types/flatbuffers": "^1.10.0",
"@types/is-number": "^7.0.1",
"@types/jest": "^26.0.24",
"@types/jest-environment-puppeteer": "^4.4.1",
"@types/lodash.clonedeep": "^4.5.6",
"@types/lodash.difference": "^4.5.6",
"@types/lodash.every": "^4.6.6",
"@types/lodash.filter": "^4.6.6",
"@types/lodash.foreach": "^4.5.6",
"@types/lodash.isnumber": "^3.0.6",
"@types/lodash.map": "^4.6.13",
"@types/lodash.pull": "^4.1.6",
"@types/lodash.sortby": "^4.7.6",
"@types/lodash.uniq": "^4.5.6",
"@types/lodash.zip": "^4.2.6",
"@types/pako": "^1.0.2",
"@types/puppeteer": "^5.4.4",
"@types/react": "^17.0.14",
"@types/react-dom": "^17.0.9",
"@types/react-helmet": "^6.1.2",
"@types/react-redux": "^7.1.18",
"@types/sha1": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.1.0",
"babel-loader": "^8.1.0",
@@ -132,7 +103,7 @@
"codecov": "^3.7.1",
"css-loader": "^5.2.4",
"eslint": "^7.24.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^8.2.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-compat": "^3.8.0",
@@ -172,7 +143,6 @@
"script-ext-html-webpack-plugin": "^2.1.4",
"serve-favicon": "^2.5.0",
"terser-webpack-plugin": "^5.1.1",
"typescript": "^4.3.5",
"webpack": "^5.34.0",
"webpack-cli": "^4.6.0",
"webpack-dev-middleware": "^4.1.0",
@@ -180,10 +150,10 @@
},
"jest": {
"testMatch": [
"**/__tests__/**/?(*.)(spec|test).ts?(x)"
"**/__tests__/**/?(*.)(spec|test).js?(x)"
],
"setupFiles": [
"./__tests__/setupMissingGlobals.ts"
"./__tests__/setupMissingGlobals.js"
],
"coverageDirectory": "./coverage/",
"collectCoverage": true
@@ -193,8 +163,7 @@
"test": {
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-function-bind",
@@ -216,6 +185,12 @@
"loose": true
}
],
[
"@babel/plugin-proposal-private-property-in-object",
{
"loose": true
}
],
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-transform-react-constant-elements",
"@babel/plugin-transform-runtime",

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

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

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

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

View File

@@ -21,21 +21,12 @@ The behavior manifest in these action creators:
Note that crossfilter indices are lazy created, as needed.
*/
import { Dataframe } from "../util/dataframe";
export const genesetDelete =
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
(genesetName: any) => (dispatch: any, getState: any) => {
export const genesetDelete = (genesetName) => (dispatch, getState) => {
const state = getState();
const { genesets } = state;
const gs = genesets?.genesets?.get(genesetName) ?? {};
const geneSymbols = Array.from(gs.genes.keys());
const obsCrossfilter = dropGeneset(
dispatch,
state,
genesetName,
geneSymbols
);
const obsCrossfilter = dropGeneset(dispatch, state, genesetName, geneSymbols);
if (genesetName === state.colors.colorAccessor) {
dispatch({
type: "reset colorscale",
@@ -50,16 +41,14 @@ export const genesetDelete =
};
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: any, genes: any) => async (dispatch: any, getState: any) => {
(genesetName, genes) => async (dispatch, getState) => {
const state = getState();
const { obsCrossfilter: prevObsCrossfilter, annoMatrix } = state;
const { schema } = annoMatrix;
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();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
genes = genes.reduce((acc: any, gene: any) => {
genes = genes.reduce((acc, gene) => {
if (geneNames.indexOf(gene.geneSymbol) === -1) {
postUserErrorToast(
`${gene.geneSymbol} doesn't appear to be a valid gene name.`
@@ -88,8 +77,7 @@ export const genesetAddGenes =
};
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: any, geneSymbols: any) => (dispatch: any, getState: any) => {
(genesetName, geneSymbols) => (dispatch, getState) => {
const state = getState();
const obsCrossfilter = dropGeneset(
dispatch,
@@ -110,14 +98,7 @@ export const genesetDeleteGenes =
Private
*/
function dropGenesetSummaryDimension(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
obsCrossfilter: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
state: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
genesetName: any
) {
function dropGenesetSummaryDimension(obsCrossfilter, state, genesetName) {
const { annoMatrix, genesets } = state;
const varIndex = annoMatrix.schema.annotations?.var?.index;
const gs = genesets?.genesets?.get(genesetName) ?? {};
@@ -133,8 +114,7 @@ function dropGenesetSummaryDimension(
return obsCrossfilter.dropDimension("X", query);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function dropGeneDimension(obsCrossfilter: any, state: any, gene: any) {
function dropGeneDimension(obsCrossfilter, state, gene) {
const { annoMatrix } = state;
const varIndex = annoMatrix.schema.annotations?.var?.index;
const query = {
@@ -147,21 +127,10 @@ function dropGeneDimension(obsCrossfilter: any, state: any, gene: any) {
return obsCrossfilter.dropDimension("X", query);
}
function dropGeneset(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
dispatch: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
state: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
genesetName: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
geneSymbols: any
) {
function dropGeneset(dispatch, state, genesetName, geneSymbols) {
const { obsCrossfilter: prevObsCrossfilter } = state;
const obsCrossfilter = geneSymbols.reduce(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(crossfilter: any, gene: any) =>
dropGeneDimension(crossfilter, state, gene),
(crossfilter, gene) => dropGeneDimension(crossfilter, state, gene),
dropGenesetSummaryDimension(prevObsCrossfilter, state, genesetName)
);
dispatch({
@@ -169,8 +138,7 @@ function dropGeneset(
continuousNamespace: { isGeneSetSummary: true },
selection: genesetName,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
geneSymbols.forEach((g: any) =>
geneSymbols.forEach((g) =>
dispatch({
type: "continuous metadata histogram cancel",
continuousNamespace: { isUserDefined: true },

View File

@@ -1,4 +1,3 @@
import type { Config } from "../globals";
import * as globals from "../globals";
import { AnnoMatrixLoader, AnnoMatrixObsCrossfilter } from "../annoMatrix";
import {
@@ -12,12 +11,8 @@ import * as annoActions from "./annotation";
import * as viewActions from "./viewStack";
import * as embActions from "./embedding";
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: any) {
function setGlobalConfig(config) {
/**
* Set any global run-time config not _exclusively_ managed by the config reducer.
* This should only set fields defined in globals.globalConfig.
@@ -30,8 +25,7 @@ function setGlobalConfig(config: any) {
/*
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: any) {
async function userColorsFetchAndLoad(dispatch) {
return fetchJson("colors").then((response) =>
dispatch({
type: "universe: user color load success",
@@ -40,12 +34,12 @@ async function userColorsFetchAndLoad(dispatch: any) {
);
}
async function schemaFetch(): Promise<{ schema: Schema }> {
return fetchJson<{ schema: Schema }>("schema");
async function schemaFetch() {
return fetchJson("schema");
}
async function configFetch(dispatch: AppDispatch): Promise<Config> {
const response = await fetchJson<{ config: globals.Config }>("config");
async function configFetch(dispatch) {
return fetchJson("config").then((response) => {
const config = { ...globals.configDefaults, ...response.config };
setGlobalConfig(config);
@@ -55,23 +49,21 @@ async function configFetch(dispatch: AppDispatch): Promise<Config> {
config,
});
return config;
});
}
async function userInfoFetch(dispatch: AppDispatch): Promise<UserInfoPayload> {
return fetchJson<{ userinfo: UserInfoPayload }>("userinfo").then(
(response) => {
async function userInfoFetch(dispatch) {
return fetchJson("userinfo").then((response) => {
const { userinfo: userInfo } = response || {};
dispatch({
type: "userInfo load complete",
userInfo,
});
return userInfo;
}
);
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
async function genesetsFetch(dispatch: any, config: any) {
async function genesetsFetch(dispatch, config) {
/* request genesets ONLY if the backend supports the feature */
const defaultResponse = {
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: any) {
function prefetchEmbeddings(annoMatrix) {
/*
prefetch requests for all embeddings
*/
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: any) => v.name);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
available.forEach((embName: any) => annoMatrix.prefetch("emb", embName));
const available = schema.layout.obs.map((v) => v.name);
available.forEach((embName) => annoMatrix.prefetch("emb", embName));
}
/*
Application bootstrap
*/
const doInitialDataLoad = (): ((
dispatch: AppDispatch,
getState: () => RootState
) => void) =>
catchErrorsWrap(async (dispatch: AppDispatch) => {
const doInitialDataLoad = () =>
catchErrorsWrap(async (dispatch) => {
dispatch({ type: "initial data load start" });
try {
const [config, schema] = await Promise.all([
configFetch(dispatch),
schemaFetch(),
schemaFetch(dispatch),
userColorsFetchAndLoad(dispatch),
userInfoFetch(dispatch),
]);
@@ -140,7 +126,7 @@ const doInitialDataLoad = (): ((
const layoutSchema = schema?.schema?.layout?.obs ?? [];
if (
defaultEmbedding &&
layoutSchema.some((s: EmbeddingSchema) => s.name === defaultEmbedding)
layoutSchema.some((s) => s.name === defaultEmbedding)
) {
dispatch(embActions.layoutChoiceAction(defaultEmbedding));
}
@@ -149,25 +135,21 @@ const doInitialDataLoad = (): ((
}
}, 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: any) {
function requestSingleGeneExpressionCountsForColoringPOST(gene) {
return {
type: "color by expression",
gene,
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
const requestUserDefinedGene = (gene: any) => ({
const requestUserDefinedGene = (gene) => ({
type: "request user defined gene success",
data: {
genes: [gene],
},
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const dispatchDiffExpErrors = (dispatch: any, response: any) => {
const dispatchDiffExpErrors = (dispatch, response) => {
switch (response.status) {
case 403:
dispatchNetworkErrorMessageToUser(
@@ -191,16 +173,8 @@ const dispatchDiffExpErrors = (dispatch: any, response: any) => {
};
const requestDifferentialExpression =
(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
set1: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
set2: any,
num_genes = 50
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
) =>
// 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) => {
(set1, set2, num_genes = 50) =>
async (dispatch, getState) => {
dispatch({ type: "request differential expression started" });
try {
/*
@@ -248,9 +222,7 @@ const requestDifferentialExpression =
const varIndex = await annoMatrix.fetch("var", varIndexName);
const diffexpLists = { negative: [], positive: [] };
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
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
diffexpLists[polarity] = response[polarity].map((v: any) => [
diffexpLists[polarity] = response[polarity].map((v) => [
varIndex.at(v[0], varIndexName),
...v.slice(1),
]);
@@ -269,10 +241,10 @@ const requestDifferentialExpression =
}
};
function fetchJson<T>(pathAndQuery: string): Promise<T> {
return doJsonRequest<T>(
function fetchJson(pathAndQuery) {
return doJsonRequest(
`${globals.API.prefix}${globals.API.version}${pathAndQuery}`
) as Promise<T>;
);
}
export default {

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

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

View File

@@ -18,13 +18,7 @@ import {
_userResetSubsetAnnoMatrix,
} 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: 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
) => {
export const clipAction = (min, max) => (dispatch, getState) => {
/*
apply a clip to the current annoMatrix. By convention, the clip
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: any, getState: any) => {
export const subsetAction = () => (dispatch, getState) => {
/*
Subset the annoMatrix to the current crossfilter selection by pushing a
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
off and re-apply
*/
const {
annoMatrix: prevAnnoMatrix,
obsCrossfilter: prevObsCrossfilter,
} = getState();
const { annoMatrix: prevAnnoMatrix, obsCrossfilter: prevObsCrossfilter } =
getState();
const annoMatrix = _userSubsetAnnoMatrix(
prevAnnoMatrix,
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: any, getState: any) => {
export const resetSubsetAction = () => (dispatch, getState) => {
/*
Reset the annoMatrix to all data. Because we may have multiple views
stacked, we pop them all. By convention, any clip transformation will

View File

@@ -2,9 +2,6 @@ import {
Dataframe,
IdentityInt32Index,
dataframeMemo,
LabelType,
DataframeValue,
DataframeValueArray,
} from "../util/dataframe";
import {
_getColumnDimensionNames,
@@ -13,71 +10,13 @@ import {
_getWritableColumns,
} from "./schema";
import { indexEntireSchema } from "../util/stateManager/schemaHelpers";
import {
_whereCacheGet,
_whereCacheMerge,
WhereCache,
WhereCacheColumnLabels,
} from "./whereCache";
import { _whereCacheGet, _whereCacheMerge } from "./whereCache";
import _shallowClone from "./clone";
import { _queryValidate, _queryCacheKey, Query } 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";
import { _queryValidate, _queryCacheKey } from "./query";
const _dataframeCache = dataframeMemo(128);
interface Cache {
[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>;
export default class AnnoMatrix {
/*
Abstract base class for all AnnoMatrix objects. This class provides a proxy
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
etc.
*/
static fields(): Field[] {
static fields() {
/*
return the fields present in the AnnoMatrix instance.
*/
return [Field.obs, Field.var, Field.emb, Field.X];
return ["obs", "var", "emb", "X"];
}
constructor(
schema: RawSchema,
nObs: number,
nVar: number,
rowIndex: LabelIndexBase | null = null
) {
constructor(schema, nObs, nVar, rowIndex = null) {
/*
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).
The row index labels are as defined by the base dataset from the server.
* 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
off of an annoMatrix, and have propagated by the (shallow) cloning protocol.
*/
@@ -145,7 +79,7 @@ export default abstract class AnnoMatrix {
this.nVar = nVar;
this.rowIndex = rowIndex || new IdentityInt32Index(nObs);
this.isView = false;
this.viewOf = this;
this.viewOf = undefined;
this.userFlags = {};
/*
@@ -168,14 +102,14 @@ export default abstract class AnnoMatrix {
emb: {},
X: {},
};
this._whereCache = {} as WhereCache;
this._whereCache = {};
this._gcInfo = new Map();
}
/**
** Schema helper/accessors
**/
getMatrixColumns(field: Field): string[] {
getMatrixColumns(field) {
/*
Return array of column names in the field. ONLY supported on the
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
getMatrixFields(): Field[] {
getMatrixFields() {
/*
Return array of fields in this annoMatrix. Currently hard-wired to
return: ["X", "obs", "var", "emb"].
@@ -198,7 +132,7 @@ export default abstract class AnnoMatrix {
return AnnoMatrix.fields();
}
getColumnSchema(field: Field, col: LabelType): ArraySchema {
getColumnSchema(field, col) {
/*
Return the schema for the field & column ,eg,
@@ -210,7 +144,7 @@ export default abstract class AnnoMatrix {
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,
this just return the column name. Multi-dimensional columns, such as embeddings,
@@ -228,19 +162,19 @@ export default abstract class AnnoMatrix {
/**
** General utility methods
**/
base(): AnnoMatrix {
base() {
/*
return the base of view, or `this` if not a view.
*/
let annoMatrix = this._getViewOf();
while (annoMatrix.isView) annoMatrix = annoMatrix._getViewOf();
let annoMatrix = this;
while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf;
return annoMatrix;
}
/**
** 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.
Currently supports ONLY full column query.
@@ -269,7 +203,7 @@ export default abstract class AnnoMatrix {
1. Fetch the "n_genes" column the "obs":
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
the columns:
@@ -297,7 +231,7 @@ export default abstract class AnnoMatrix {
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
not return a value.
@@ -306,6 +240,7 @@ export default abstract class AnnoMatrix {
overall component rendering latency.
*/
this._fetch(field, q);
return undefined;
}
/**
@@ -326,6 +261,8 @@ export default abstract class AnnoMatrix {
** The actual implementation is in the sub-classes, which MUST override these.
**/
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
addObsAnnoCategory(col, category) {
/*
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
@@ -338,8 +275,11 @@ export default abstract class AnnoMatrix {
addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix
*/
abstract addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix;
_subclassResponsibility();
}
// eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements
async removeObsAnnoCategory(col, category, unassignedCategory) {
/*
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.
@@ -356,12 +296,11 @@ export default abstract class 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>;
_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.
@@ -373,8 +312,11 @@ export default abstract class AnnoMatrix {
dropObsColumn("old annotations") -> AnnoMatrix
*/
abstract dropObsColumn(col: LabelType): 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.
@@ -399,12 +341,11 @@ export default abstract class AnnoMatrix {
) -> AnnoMatrix
*/
abstract addObsColumn<T extends DataframeValueArray>(
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): 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.
@@ -415,11 +356,14 @@ export default abstract class AnnoMatrix {
renameObsColumn('cell type', 'old cell type') -> AnnoMatrix.
*/
abstract renameObsColumn(oldCol: LabelType, newCol: LabelType): 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 annotation
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.
@@ -428,13 +372,13 @@ export default abstract class AnnoMatrix {
Example:
await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix
*/
abstract setObsColumnValues(
col: LabelType,
obsLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrix>;
*/
_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.
@@ -447,12 +391,11 @@ export default abstract class AnnoMatrix {
await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix
*/
abstract resetObsColumnValues<T extends DataframeValue>(
col: LabelType,
oldValue: T,
newValue: T
): Promise<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.
@@ -461,12 +404,10 @@ export default abstract class AnnoMatrix {
Will throw if the column schema is invalid (eg, duplicate name).
*/
abstract addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix;
_subclassResponsibility();
}
getCacheKeys(
field: Field,
query: Query
): WhereCacheColumnLabels | [undefined] {
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).
@@ -477,21 +418,19 @@ Return cache keys for columns associated with this query. May return
/**
** Private interfaces below.
**/
_resolveCachedQueries(field: Field, queries: Query[]): LabelArray {
_resolveCachedQueries(field, queries) {
return queries
.map((query: Query) =>
// @ts-expect-error --- TODO revisit:
// `filter`: This expression is not callable.
.map((query) =>
_whereCacheGet(this._whereCache, this.schema, field, query).filter(
(cacheKey?: LabelType) =>
(cacheKey) =>
cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
)
)
.flat();
}
async _fetch(field: Field, q: Query | Query[]): Promise<Dataframe> {
if (!AnnoMatrix.fields().includes(field)) return Dataframe.empty();
async _fetch(field, q) {
if (!AnnoMatrix.fields().includes(field)) return undefined;
const queries = Array.isArray(q) ? q : [q];
queries.forEach(_queryValidate);
@@ -502,7 +441,7 @@ Return cache keys for columns associated with this query. May return
/* find any query not already cached */
const uncachedQueries = queries.filter((query) =>
_whereCacheGet(this._whereCache, this.schema, field, query).some(
(cacheKey?: LabelType) =>
(cacheKey) =>
cacheKey === undefined || !this._cache[field].hasCol(cacheKey)
)
);
@@ -511,10 +450,7 @@ Return cache keys for columns associated with this query. May return
if (uncachedQueries.length > 0) {
await Promise.all(
uncachedQueries.map((query) =>
this._getPendingLoad(
field,
query,
async (_field: Field, _query: Query): Promise<void> => {
this._getPendingLoad(field, query, async (_field, _query) => {
/* fetch, then index. _doLoad is subclass interface */
const [whereCacheUpdate, df] = await this._doLoad(_field, _query);
this._cache[_field] = this._cache[_field].withColsFrom(df);
@@ -522,8 +458,7 @@ Return cache keys for columns associated with this query. May return
this._whereCache,
whereCacheUpdate
);
}
)
})
)
);
}
@@ -537,11 +472,7 @@ Return cache keys for columns associated with this query. May return
return response;
}
async _getPendingLoad(
field: Field,
query: Query,
fetchFn: (_field: Field, _query: Query) => Promise<void>
): Promise<void> {
async _getPendingLoad(field, query, fetchFn) {
/*
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
@@ -562,22 +493,9 @@ Return cache keys for columns associated with this query. May return
return this._pendingLoad[field][key];
}
abstract _doLoad(
field: Field,
query: Query
): 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;
// eslint-disable-next-line class-methods-use-this -- make sure subclass implements
async _doLoad() {
_subclassResponsibility();
}
/**
@@ -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,
as much of the cache is pinned by that data structure.
*/
_gcField(field: Field, isHot: boolean, pinnedColumns: LabelArray): void {
const maxColumns = isHot ? 256 : 10;
_gcField(field, isHot, pinnedColumns) {
const maxColumns = isHot ? 256 : 10; // maybe to aggressive?
const cache = this._cache[field];
if (cache.colIndex.size() < maxColumns) return; // trivial rejection
const candidates = cache.colIndex
.labels()
// @ts-expect-error --- TODO revisit:
// `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));
.filter((col) => !pinnedColumns.includes(col));
const excessCount = candidates.length + pinnedColumns.length - maxColumns;
if (excessCount > 0) {
const { _gcInfo } = this;
candidates.sort((a: LabelType, b: LabelType) => {
candidates.sort((a, b) => {
let atime = _gcInfo.get(_columnCacheKey(field, a));
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(
(df: Dataframe, col: LabelType) => df.dropCol(col),
(df, col) => df.dropCol(col),
this._cache[field]
);
toDrop.forEach((col: LabelType) =>
_gcInfo.delete(_columnCacheKey(field, col))
);
toDrop.forEach((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
only want to gc X.
*/
if (field === Field.X) {
if (field === "X") {
this._gcField(
field,
true,
// @ts-expect-error --- TODO revisit:
// Property 'concat' does not exist on type 'LabelArray'.
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,
or false if we are in some other context (eg, history state).
*/
const { isHot } = hints;
const candidateFields = isHot
? [Field.X]
: [Field.X, Field.emb, Field.var, Field.obs];
const candidateFields = isHot ? ["X"] : ["X", "emb", "var", "obs"];
candidateFields.forEach((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
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 { _gcInfo } = this;
const now = Date.now();
cols.forEach((c: LabelType) => {
cols.forEach((c) => {
_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();
**/
_cloneDeeper(clone: AnnoMatrix): AnnoMatrix {
_cloneDeeper(clone) {
clone._cache = _shallowClone(this._cache);
clone._gcInfo = new Map();
clone._pendingLoad = {
@@ -720,7 +629,7 @@ Return cache keys for columns associated with this query. May return
return clone;
}
_clone(): AnnoMatrix {
_clone() {
const clone = _shallowClone(this);
this._cloneDeeper(clone);
Object.seal(clone);
@@ -731,6 +640,11 @@ Return cache keys for columns associated with this query. May return
/*
private utility functions below
*/
function _columnCacheKey(field: Field, column: LabelType): string {
function _columnCacheKey(field, column) {
return `${field}/${column}`;
}
function _subclassResponsibility() {
/* protect against bugs in subclass */
throw new Error("subclass failed to implement required method");
}

View File

@@ -1,7 +1,6 @@
/*
Shallow clone an object, correctly handling prototype
*/
export default function _shallowClone<T>(orig: T): T {
export default function _shallowClone(orig) {
return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig);
}

View File

@@ -9,57 +9,26 @@ AnnoMatrix stay in sync:
*/
import Crossfilter from "../util/typedCrossfilter";
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 =
| [string, DataframeValueArray, DataframeValueArray]
| [string, DataframeValueArray]
| [string, DataframeValueArray, Int32ArrayConstructor]
| [string, DataframeValueArray, Float32ArrayConstructor];
function _dimensionNameFromDf(field: Field, df: Dataframe): string {
function _dimensionNameFromDf(field, df) {
const colNames = df.colIndex.labels();
return _dimensionName(field, colNames);
}
function _dimensionName(
field: Field,
colNames: LabelType | LabelArray
): string {
function _dimensionName(field, colNames) {
if (!Array.isArray(colNames)) return `${field}/${colNames}`;
return `${field}/${colNames.join(":")}`;
}
export default class AnnoMatrixObsCrossfilter {
annoMatrix: AnnoMatrix;
obsCrossfilter: Crossfilter;
constructor(
annoMatrix: AnnoMatrix,
_obsCrossfilter: Crossfilter | null = null
) {
constructor(annoMatrix, _obsCrossfilter = null) {
this.annoMatrix = annoMatrix;
this.obsCrossfilter =
_obsCrossfilter || new Crossfilter(annoMatrix._cache.obs);
this.obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs);
}
size(): number {
size() {
return this.obsCrossfilter.size();
}
@@ -70,33 +39,26 @@ export default class AnnoMatrixObsCrossfilter {
See API documentation in annoMatrix.js.
**/
addObsColumn<T extends DataframeValueArray>(
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): AnnoMatrixObsCrossfilter {
addObsColumn(colSchema, Ctor, value) {
const annoMatrix = this.annoMatrix.addObsColumn(colSchema, Ctor, value);
const obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs);
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
dropObsColumn(col: LabelType): AnnoMatrixObsCrossfilter {
dropObsColumn(col) {
const annoMatrix = this.annoMatrix.dropObsColumn(col);
let { obsCrossfilter } = this;
const dimName = _dimensionName(Field.obs, col);
const dimName = _dimensionName("obs", col);
if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName);
}
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
renameObsColumn(
oldCol: LabelType,
newCol: LabelType
): AnnoMatrixObsCrossfilter {
renameObsColumn(oldCol, newCol) {
const annoMatrix = this.annoMatrix.renameObsColumn(oldCol, newCol);
const oldDimName = _dimensionName(Field.obs, oldCol);
const newDimName = _dimensionName(Field.obs, newCol);
const oldDimName = _dimensionName("obs", oldCol);
const newDimName = _dimensionName("obs", newCol);
let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(oldDimName)) {
obsCrossfilter = obsCrossfilter.renameDimension(oldDimName, newDimName);
@@ -104,12 +66,9 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
addObsAnnoCategory(
col: LabelType,
category: string
): AnnoMatrixObsCrossfilter {
addObsAnnoCategory(col, category) {
const annoMatrix = this.annoMatrix.addObsAnnoCategory(col, category);
const dimName = _dimensionName(Field.obs, col);
const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -117,17 +76,13 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
async removeObsAnnoCategory(
col: LabelType,
category: string,
unassignedCategory: string
): Promise<AnnoMatrixObsCrossfilter> {
async removeObsAnnoCategory(col, category, unassignedCategory) {
const annoMatrix = await this.annoMatrix.removeObsAnnoCategory(
col,
category,
unassignedCategory
);
const dimName = _dimensionName(Field.obs, col);
const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -135,17 +90,13 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
async setObsColumnValues(
col: LabelType,
rowLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrixObsCrossfilter> {
async setObsColumnValues(col, rowLabels, value) {
const annoMatrix = await this.annoMatrix.setObsColumnValues(
col,
rowLabels,
value
);
const dimName = _dimensionName(Field.obs, col);
const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -153,17 +104,13 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
async resetObsColumnValues<T extends DataframeValue>(
col: LabelType,
oldValue: T,
newValue: T
): Promise<AnnoMatrixObsCrossfilter> {
async resetObsColumnValues(col, oldValue, newValue) {
const annoMatrix = await this.annoMatrix.resetObsColumnValues(
col,
oldValue,
newValue
);
const dimName = _dimensionName(Field.obs, col);
const dimName = _dimensionName("obs", col);
let { obsCrossfilter } = this;
if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName);
@@ -171,25 +118,23 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrixObsCrossfilter {
addEmbedding(colSchema) {
const annoMatrix = this.annoMatrix.addEmbedding(colSchema);
return new AnnoMatrixObsCrossfilter(annoMatrix, this.obsCrossfilter);
}
/**
* 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.
*/
dropDimension(field: Field, query: Query): AnnoMatrixObsCrossfilter {
dropDimension(field, query) {
const { annoMatrix } = this;
let { obsCrossfilter } = this;
const keys = annoMatrix
.getCacheKeys(field, query)
// @ts-expect-error ts-migrate --- suppressing TS defect (https://github.com/microsoft/TypeScript/issues/44373).
// Compiler is complaining that expression is not callable on array union types. Remove suppression once fixed.
.filter((k?: string | number) => k !== undefined);
const dimName = _dimensionName(field, keys as string[]);
.filter((k) => k !== undefined);
const dimName = _dimensionName(field, keys);
if (obsCrossfilter.hasDimension(dimName)) {
obsCrossfilter = obsCrossfilter.delDimension(dimName);
}
@@ -201,12 +146,7 @@ export default class AnnoMatrixObsCrossfilter {
are just wrappers to lazy create indices.
**/
async select(
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> {
async select(field, query, spec) {
const { annoMatrix } = this;
let { obsCrossfilter } = this;
@@ -219,9 +159,7 @@ export default class AnnoMatrixObsCrossfilter {
// grab the data, so we can grab the index.
const df = await annoMatrix.fetch(field, query);
if (!df) {
throw new Error("Dataframe cannot be `undefined`");
}
const dimName = _dimensionNameFromDf(field, df);
if (!obsCrossfilter.hasDimension(dimName)) {
// lazy index generation - add dimension when first used
@@ -238,26 +176,23 @@ export default class AnnoMatrixObsCrossfilter {
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
selectAll(): AnnoMatrixObsCrossfilter {
selectAll() {
/*
Select all on any dimension in this field.
*/
const { annoMatrix } = this;
const currentDims = this.obsCrossfilter.dimensionNames();
const obsCrossfilter = currentDims.reduce(
(xfltr, dim) => xfltr.select(dim, { mode: "all" }),
this.obsCrossfilter
);
const obsCrossfilter = currentDims.reduce((xfltr, dim) => xfltr.select(dim, { mode: "all" }), this.obsCrossfilter);
return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter);
}
countSelected(): number {
countSelected() {
/* if no data yet indexed in the crossfilter, just say everything is selected */
if (this.obsCrossfilter.size() === 0) return this.annoMatrix.nObs;
return this.obsCrossfilter.countSelected();
}
allSelectedMask(): Uint8Array {
allSelectedMask() {
/* if no data yet indexed in the crossfilter, just say everything is selected */
if (
this.obsCrossfilter.size() === 0 ||
@@ -269,7 +204,7 @@ export default class AnnoMatrixObsCrossfilter {
return this.obsCrossfilter.allSelectedMask();
}
allSelectedLabels(): LabelArray {
allSelectedLabels() {
/* if no data yet indexed in the crossfilter, just say everything is selected */
if (
this.obsCrossfilter.size() === 0 ||
@@ -283,18 +218,12 @@ export default class AnnoMatrixObsCrossfilter {
return index.labels();
}
fillByIsSelected<A extends TypedArray>(
array: A,
selectedValue: A[0],
deselectedValue: A[0]
): A {
fillByIsSelected(array, selectedValue, deselectedValue) {
/* if no data yet indexed in the crossfilter, just say everything is selected */
if (
this.obsCrossfilter.size() === 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 this.obsCrossfilter.fillByIsSelected(
@@ -308,35 +237,25 @@ export default class AnnoMatrixObsCrossfilter {
** Private below
**/
_addObsCrossfilterDimension(
annoMatrix: AnnoMatrix,
obsCrossfilter: Crossfilter,
field: Field,
df: Dataframe
): Crossfilter {
_addObsCrossfilterDimension(annoMatrix, obsCrossfilter, field, df) {
if (field === "var") return obsCrossfilter;
const dimName = _dimensionNameFromDf(field, df);
const dimParams = this._getObsDimensionParams(field, df);
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);
return obsCrossfilter;
}
_getColumnBaseType(field: Field, col: LabelType): string {
_getColumnBaseType(field, col) {
/* Look up the primitive type for this field/col */
const colSchema = _getColumnSchema(this.annoMatrix.schema, field, col);
return colSchema.type;
}
_getObsDimensionParams(
field: Field,
df: Dataframe
): ObsDimensionParams | undefined {
_getObsDimensionParams(field, df) {
/* return the crossfilter dimensiontype type and params for this field/dataframe */
if (field === Field.emb) {
if (field === "emb") {
/* assumed to be 2D */
return ["spatial", df.icol(0).asArray(), df.icol(1).asArray()];
}
@@ -344,8 +263,6 @@ export default class AnnoMatrixObsCrossfilter {
/* assumed to be 1D */
const col = df.icol(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);
if (type === "string" || type === "categorical" || type === "boolean") {
return ["enum", col.asArray()];

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

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

View File

@@ -2,47 +2,31 @@ import { doBinaryRequest, doFetch } from "./fetchHelpers";
import { matrixFBSToDataframe } from "../util/stateManager/matrix";
import { _getColumnSchema } from "./schema";
import {
addObsAnnoCategory,
addObsAnnoColumn,
addObsLayout,
removeObsAnnoCategory,
removeObsAnnoColumn,
addObsAnnoCategory,
removeObsAnnoCategory,
addObsLayout,
} from "../util/stateManager/schemaHelpers";
import { isAnyArray } from "../common/types/arraytypes";
import { _whereCacheCreate, WhereCache } from "./whereCache";
import { isArrayOrTypedArray } from "../util/typeHelpers";
import { _whereCacheCreate } from "./whereCache";
import AnnoMatrix from "./annoMatrix";
import PromiseLimit from "../util/promiseLimit";
import {
_expectComplexQuery,
_expectSimpleQuery,
_hashStringValues,
_urlEncodeComplexQuery,
_expectComplexQuery,
_urlEncodeLabelQuery,
ComplexQuery,
Query,
_urlEncodeComplexQuery,
_hashStringValues,
} from "./query";
import {
normalizeResponse,
normalizeWritableCategoricalSchema,
} from "./normalize";
import {
AnnotationColumnSchema,
Field,
EmbeddingSchema,
RawSchema,
} from "../common/types/schema";
import {
Dataframe,
DataframeValue,
DataframeValueArray,
LabelType,
} from "../util/dataframe";
const promiseThrottle = new PromiseLimit(5);
export default class AnnoMatrixLoader extends AnnoMatrix {
baseURL: string;
/*
AnnoMatrix implementation which proxies to HTTP server using the CXG REST API.
Used as the base (non-view) instance.
@@ -53,7 +37,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
new AnnoMatrixLoader(serverBaseURL, schema) -> instance
*/
constructor(baseURL: string, schema: RawSchema) {
constructor(baseURL, schema) {
const { nObs, nVar } = schema.dataframe;
super(schema, nObs, nVar);
@@ -68,36 +52,24 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
/**
** 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.
*/
const colSchema = _getColumnSchema(
this.schema,
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
const colSchema = _getColumnSchema(this.schema, "obs", col);
_writableCategoryTypeCheck(colSchema); // throws on error
const newAnnoMatrix = this._clone();
newAnnoMatrix.schema = addObsAnnoCategory(this.schema, col, category);
return newAnnoMatrix;
}
async removeObsAnnoCategory(
col: LabelType,
category: string,
unassignedCategory: string
): Promise<AnnoMatrix> {
async removeObsAnnoCategory(col, category, unassignedCategory) {
/*
Remove a single "category" (aka "label") from the data & schema of an obs column.
*/
const colSchema = _getColumnSchema(
this.schema,
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
const colSchema = _getColumnSchema(this.schema, "obs", col);
_writableCategoryTypeCheck(colSchema); // throws on error
const newAnnoMatrix = await this.resetObsColumnValues(
col,
@@ -112,16 +84,12 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix;
}
dropObsColumn(col: LabelType): AnnoMatrix {
dropObsColumn(col) {
/*
drop column from field
*/
const colSchema = _getColumnSchema(
this.schema,
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCheck(colSchema); // throws on error
const colSchema = _getColumnSchema(this.schema, "obs", col);
_writableCheck(colSchema); // throws on error
const newAnnoMatrix = this._clone();
newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col);
@@ -129,11 +97,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix;
}
addObsColumn<T extends DataframeValueArray>(
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): AnnoMatrix {
addObsColumn(colSchema, Ctor, value) {
/*
add a column to field, initializing with value. Value may
be one of:
@@ -144,7 +108,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
colSchema.writable = true;
const colName = colSchema.name;
if (
_getColumnSchema(this.schema, Field.obs, colName) ||
_getColumnSchema(this.schema, "obs", colName) ||
this._cache.obs.hasCol(colName)
) {
throw new Error("column already exists");
@@ -152,7 +116,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
const newAnnoMatrix = this._clone();
let data;
if (isAnyArray(value)) {
if (isArrayOrTypedArray(value)) {
if (value.constructor !== Ctor)
throw new Error("Mismatched value array type");
if (value.length !== this.nObs)
@@ -170,50 +134,35 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix;
}
renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix {
renameObsColumn(oldCol, newCol) {
/*
Rename the obs oldColName to newColName. oldCol must be writable.
*/
const oldColSchema = _getColumnSchema(
this.schema,
Field.obs,
oldCol
) as AnnotationColumnSchema;
_writableObsCheck(oldColSchema);
const oldColSchema = _getColumnSchema(this.schema, "obs", oldCol);
_writableCheck(oldColSchema); // throws on error
const value = this._cache.obs.hasCol(oldCol)
? this._cache.obs.col(oldCol).asArray()
: undefined;
return this.dropObsColumn(oldCol).addObsColumn(
{
...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,
},
// @ts-expect-error ts-migrate --- TODO revisit:
// `value`: Object is possibly 'undefined'.
value.constructor,
value
);
}
async setObsColumnValues(
col: LabelType,
rowLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrix> {
async setObsColumnValues(col, rowLabels, value) {
/*
Set all rows identified by rowLabels to value.
*/
const colSchema = _getColumnSchema(
this.schema,
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
const colSchema = _getColumnSchema(this.schema, "obs", col);
_writableCategoryTypeCheck(colSchema); // throws on error
// 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))
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();
for (let i = 0, len = rowIndices.length; i < len; i += 1) {
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;
}
@@ -234,29 +183,19 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix;
}
async resetObsColumnValues<T extends DataframeValue>(
col: LabelType,
oldValue: T,
newValue: T
): Promise<AnnoMatrix> {
async resetObsColumnValues(col, oldValue, newValue) {
/*
Set all rows with value 'oldValue' to 'newValue'.
*/
const colSchema = _getColumnSchema(
this.schema,
Field.obs,
col
) as AnnotationColumnSchema;
_writableObsCategoryTypeCheck(colSchema); // throws on error
const colSchema = _getColumnSchema(this.schema, "obs", col);
_writableCategoryTypeCheck(colSchema); // throws on error
// @ts-expect-error ts-migrate --- TODO revisit:
// `colSchema.categories`: Object is possibly 'undefined'.
if (!colSchema.categories.includes(oldValue)) {
throw new Error("unknown category");
}
// 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))
throw new Error("Internal error - user annotation data missing");
@@ -274,12 +213,12 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
return newAnnoMatrix;
}
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix {
addEmbedding(colSchema) {
/*
add new layout to the obs embeddings
*/
const { name: colName } = colSchema;
if (_getColumnSchema(this.schema, Field.emb, colName)) {
if (_getColumnSchema(this.schema, "emb", colName)) {
throw new Error("column already exists");
}
@@ -291,10 +230,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
/**
** Private below
**/
async _doLoad(
field: Field,
query: Query
): Promise<[WhereCache | null, Dataframe]> {
async _doLoad(field, query) {
/*
_doLoad - evaluates the query against the field. Returns:
* whereCache update: column query map mapping the query to the column labels
@@ -321,9 +257,8 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
default:
throw new Error("Unknown field name");
}
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);
if (!result || result.isEmpty()) throw Error("Unknown field/col");
@@ -333,7 +268,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
result.colIndex.labels()
);
result = normalizeResponse(field, this.schema, result);
result = normalizeResponse(field, query, this.schema, result);
return [whereCacheUpdate, result];
}
@@ -343,26 +278,20 @@ export default class AnnoMatrixLoader extends AnnoMatrix {
Utility functions below
*/
function _writableObsCheck(obsColSchema: AnnotationColumnSchema): void {
if (!obsColSchema?.writable) {
function _writableCheck(colSchema) {
if (!colSchema?.writable) {
throw new Error("Unknown or readonly obs column");
}
}
function _writableObsCategoryTypeCheck(
obsColSchema: AnnotationColumnSchema
): void {
_writableObsCheck(obsColSchema);
if (obsColSchema.type !== "categorical") {
function _writableCategoryTypeCheck(colSchema) {
_writableCheck(colSchema);
if (colSchema.type !== "categorical") {
throw new Error("column must be categorical");
}
}
function _embLoader(
baseURL: string,
_field: Field,
query: Query
): () => Promise<ArrayBuffer> {
function _embLoader(baseURL, _field, query) {
_expectSimpleQuery(query);
const urlBase = `${baseURL}layout/obs`;
@@ -371,11 +300,7 @@ function _embLoader(
return () => doBinaryRequest(url);
}
function _obsOrVarLoader(
baseURL: string,
field: Field,
query: Query
): () => Promise<ArrayBuffer> {
function _obsOrVarLoader(baseURL, field, query) {
_expectSimpleQuery(query);
const urlBase = `${baseURL}annotations/${field}`;
@@ -384,26 +309,19 @@ function _obsOrVarLoader(
return () => doBinaryRequest(url);
}
function _XLoader(
baseURL: string,
_field: Field,
query: Query
): () => Promise<ArrayBuffer> {
function _XLoader(baseURL, field, query) {
_expectComplexQuery(query);
// Casting here as query is validated to be complex in _expectComplexQuery above.
const complexQuery = query as ComplexQuery;
if ("where" in complexQuery) {
if (query.where) {
const urlBase = `${baseURL}data/var`;
const urlQuery = _urlEncodeComplexQuery(complexQuery);
const urlQuery = _urlEncodeComplexQuery(query);
const url = `${urlBase}?${urlQuery}`;
return () => doBinaryRequest(url);
}
if ("summarize" in complexQuery) {
if (query.summarize) {
const urlBase = `${baseURL}summarize/var`;
const urlQuery = _urlEncodeComplexQuery(complexQuery);
const urlQuery = _urlEncodeComplexQuery(query);
if (urlBase.length + urlQuery.length < 2000) {
const url = `${urlBase}?${urlQuery}`;

View File

@@ -11,15 +11,7 @@ Undoable metareducer and the AnnoMatrix private API. It would be helpful
to make the Undoable interface better factored.
*/
import { Action, Dispatch, MiddlewareAPI } from "redux";
import AnnoMatrix from "./annoMatrix";
import { GCHints } from "../common/types/entities";
const annoMatrixGC =
(store: MiddlewareAPI) =>
// GC middleware doesn't add any extra types to dispatch; it just executes GC and continues.
(next: Dispatch) =>
(action: Action): Action => {
const annoMatrixGC = (store) => (next) => (action) => {
if (_itIsTimeForGC()) {
_doGC(store);
}
@@ -28,7 +20,7 @@ const annoMatrixGC =
let lastGCTime = 0;
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.
@@ -42,22 +34,17 @@ function _itIsTimeForGC(): boolean {
return false;
}
function _doGC(store: MiddlewareAPI): void {
function _doGC(store) {
const state = store.getState();
// 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 undoableFuture = state["@@undoable/future"];
const undoableStack = undoablePast
.concat(undoableFuture)
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers
.flatMap((snapshot: any) =>
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])
.flatMap((snapshot) =>
snapshot.filter((v) => v[0] === "annoMatrix").map((v) => v[1])
);
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,
as our current gc algo is more aggressive with those not hot.
*/
const allAnnoMatrices = new Map<AnnoMatrix, GCHints>(
undoableStack.map((m: AnnoMatrix) => [m, { isHot: false }])
const allAnnoMatrices = new Map(
undoableStack.map((m) => [m, { isHot: false }])
);
let am = currentAnnoMatrix;
while (am?.isView) {
while (am) {
allAnnoMatrices.set(am, { isHot: true });
am = am.viewOf;
}
allAnnoMatrices.forEach((hints, annoMatrix: AnnoMatrix) =>
annoMatrix._gc(hints)
);
allAnnoMatrices.forEach((hints, annoMatrix) => annoMatrix._gc(hints));
}
export default annoMatrixGC;

View File

@@ -5,19 +5,8 @@ import {
overflowCategoryLabel,
globalConfig,
} from "../globals";
import { Dataframe, LabelType, DataframeColumn } from "../util/dataframe";
import {
AnnotationColumnSchema,
ArraySchema,
Field,
Schema,
} from "../common/types/schema";
export function normalizeResponse(
field: Field,
schema: Schema,
response: Dataframe
): Dataframe {
export function normalizeResponse(field, query, schema, response) {
/**
* There are a number of assumptions in the front-end about data typing and data
* 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
if (field !== Field.obs && field !== Field.var) return response;
if (field !== "obs" && field !== "var") return response;
const colLabels = response.colIndex.labels();
for (const colLabel of colLabels) {
const colSchema = _getColumnSchema(
schema,
field,
colLabel
) as AnnotationColumnSchema;
const colSchema = _getColumnSchema(schema, field, colLabel);
const isIndex = _isIndex(schema, field, colLabel);
const { type, writable } = colSchema;
@@ -74,7 +59,7 @@ export function normalizeResponse(
return response;
}
function castColumnToBoolean(df: Dataframe, label: LabelType): Dataframe {
function castColumnToBoolean(df, label) {
const colData = df.col(label).asArray();
const newColData = new Array(colData.length);
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;
}
export function normalizeWritableCategoricalSchema(
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 {
export function normalizeWritableCategoricalSchema(colSchema, col) {
/*
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 array is UI sorted.
*/
const categorySet = new Set<string>(
const categorySet = new Set(
col.summarizeCategorical().categories.concat(colSchema.categories ?? [])
);
if (!categorySet.has(unassignedCategoryLabel)) {
@@ -101,19 +83,15 @@ export function normalizeWritableCategoricalSchema(
return colSchema;
}
export function normalizeCategorical(
df: Dataframe,
colLabel: LabelType,
colSchema: AnnotationColumnSchema
): Dataframe {
export function normalizeCategorical(df, colLabel, colSchema) {
/*
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
of "top N" into an overflow labels.
*/
const { writable } = colSchema;
const col = df.col(colLabel);
if (writable) {
// writable (aka user) annotations
normalizeWritableCategoricalSchema(colSchema, col);
@@ -125,7 +103,7 @@ export function normalizeCategorical(
// consolidate all categories from data and schema into a single list
const colDataSummary = col.summarizeCategorical();
const allCategories = new Set<string>(
const allCategories = new Set(
colDataSummary.categories.concat(colSchema.categories ?? [])
);

View File

@@ -1,52 +1,21 @@
import sha1 from "sha1";
import { _dubEncURIComp } from "./fetchHelpers";
import { Field } from "../common/types/schema";
import { LabelType } from "../util/dataframe";
/**
* 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.
* @param {Query} query - the query
* @returns {Query} - the normalized query
* @param {object | string} query - the query
* @returns {object | string} - the normalized query
*/
export function _queryValidate(query: Query): Query {
export function _queryValidate(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");
if ("where" in query) {
if (query.where) {
const {
field: queryField,
column: queryColumn,
@@ -56,7 +25,7 @@ export function _queryValidate(query: Query): Query {
throw new Error("Incomplete where query");
return query;
}
if ("summarize" in query) {
if (query.summarize) {
const {
field: queryField,
column: queryColumn,
@@ -71,7 +40,11 @@ export function _queryValidate(query: Query): Query {
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");
}
@@ -80,12 +53,12 @@ export function _expectComplexQuery(query: Query): void {
*
* @param {string} field
* @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") {
// complex query
if ("where" in query) {
if (query.where) {
const {
field: queryField,
column: queryColumn,
@@ -93,7 +66,7 @@ export function _queryCacheKey(field: Field, query: Query): string {
} = query.where;
return `${field}/${queryField}/${queryColumn}/${queryValue}`;
}
if ("summarize" in query) {
if (query.summarize) {
const {
method,
field: queryField,
@@ -111,34 +84,34 @@ export function _queryCacheKey(field: Field, query: Query): string {
return `${field}/${query}`;
}
function _urlEncodeWhereQuery(q: WhereQueryTerm): string {
function _urlEncodeWhereQuery(q) {
const { field: queryField, column: queryColumn, value: queryValue } = q;
return `${_dubEncURIComp(queryField)}:${_dubEncURIComp(
queryColumn
)}=${_dubEncURIComp(queryValue)}`;
}
function _urlEncodeSummarizeQuery(q: SummarizeQueryTerm): string {
function _urlEncodeSummarizeQuery(q) {
const { method, field, column, values } = q;
const filter = values
.map((value: string) => _urlEncodeWhereQuery({ field, column, value }))
.map((value) => _urlEncodeWhereQuery({ field, column, value }))
.join("&");
return `method=${method}&${filter}`;
}
export function _urlEncodeComplexQuery(q: ComplexQuery): string {
export function _urlEncodeComplexQuery(q) {
if (typeof q === "object") {
if ("where" in q) {
if (q.where) {
return _urlEncodeWhereQuery(q.where);
}
if ("summarize" in q) {
if (q.summarize) {
return _urlEncodeSummarizeQuery(q.summarize);
}
}
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 (typeof q !== "string") throw new Error("Query must be a simple label.");
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.
*/
export function _hashStringValues(arrayOfString: string[]): string {
return sha1(arrayOfString.join(""));
export function _hashStringValues(arrayOfString) {
const hash = sha1(arrayOfString.join(""));
return hash;
}

View File

@@ -1,54 +1,34 @@
/*
Private helper functions related to schema
*/
import {
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 {
export function _getColumnSchema(schema, field, col) {
/* look up the column definition */
switch (field) {
case Field.obs:
case "obs":
if (typeof col === "object")
throw new Error("unable to get column schema by query");
return schema.annotations.obsByName[col];
case Field.var:
case "var":
if (typeof col === "object")
throw new Error("unable to get column schema by query");
return schema.annotations.varByName[col];
case Field.emb:
case "emb":
if (typeof col === "object")
throw new Error("unable to get column schema by query");
return schema.layout.obsByName[col];
case Field.X:
case "X":
return schema.dataframe;
default:
throw new Error(`unknown field name: ${field}`);
}
}
export function _isIndex(
schema: Schema,
field: Field.obs | Field.var,
col: LabelType
): boolean {
export function _isIndex(schema, field, col) {
const index = schema.annotations?.[field].index;
return !!(index && index === col);
return index && index === col;
}
export function _getColumnDimensionNames(
schema: Schema,
field: Field,
col: LabelType
): LabelArray | undefined {
export function _getColumnDimensionNames(schema, field, col) {
/*
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
@@ -58,33 +38,30 @@ export function _getColumnDimensionNames(
if (!colSchema) {
return undefined;
}
if ("dims" in colSchema) {
return colSchema.dims;
}
return [col];
return colSchema.dims || [col];
}
export function _schemaColumns(schema: Schema, field: Field): string[] {
export function _schemaColumns(schema, field) {
switch (field) {
case Field.obs:
case "obs":
return Object.keys(schema.annotations.obsByName);
case Field.var:
case "var":
return Object.keys(schema.annotations.varByName);
case Field.emb:
case "emb":
return Object.keys(schema.layout.obsByName);
default:
throw new Error(`unknown field name: ${field}`);
}
}
export function _getWritableColumns(schema: Schema, field: Field): string[] {
if (field !== Field.obs) return [];
export function _getWritableColumns(schema, field) {
if (field !== "obs") return [];
return schema.annotations.obs.columns
.filter((v: AnnotationColumnSchema) => v.writable)
.map((v: AnnotationColumnSchema) => v.name);
.filter((v) => v.writable)
.map((v) => v.name);
}
export function _isContinuousType(schema: ArraySchema): boolean {
export function _isContinuousType(schema) {
const { type } = schema;
return !(type === "string" || type === "boolean" || type === "categorical");
}

View File

@@ -4,18 +4,8 @@ instances of AnnoMatrix, implementing common UI functions.
*/
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(
annoMatrix: AnnoMatrix,
obsMask: Uint8Array
): AnnoMatrixRowSubsetView {
export function isubsetMask(annoMatrix, obsMask) {
/*
Subset annomatrix to contain the rows which have truish value in the mask.
Maks length must equal annoMatrix.nObs (row count).
@@ -23,10 +13,7 @@ export function isubsetMask(
return isubset(annoMatrix, _maskToList(obsMask));
}
export function isubset(
annoMatrix: AnnoMatrix,
obsOffsets: OffsetArray
): AnnoMatrixRowSubsetView {
export function isubset(annoMatrix, obsOffsets) {
/*
Subset annomatrix to contain the positions contained in the obsOffsets array
@@ -38,10 +25,7 @@ export function isubset(
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
}
export function subset(
annoMatrix: AnnoMatrix,
obsLabels: Int32Array
): AnnoMatrixRowSubsetView {
export function subset(annoMatrix, obsLabels) {
/*
subset based on labels
*/
@@ -49,21 +33,14 @@ export function subset(
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
}
export function subsetByIndex(
annoMatrix: AnnoMatrix,
obsIndex: DenseInt32Index | IdentityInt32Index | KeyIndex
): AnnoMatrixRowSubsetView {
export function subsetByIndex(annoMatrix, obsIndex) {
/*
subset based upon the new obs index.
*/
return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex);
}
export function clip(
annoMatrix: AnnoMatrix,
qmin: number,
qmax: number
): AnnoMatrix {
export function clip(annoMatrix, qmin, qmax) {
/*
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
@@ -76,8 +53,11 @@ export function clip(
Private utility functions below
*/
function _maskToList(mask: Uint8Array): OffsetArray {
function _maskToList(mask) {
/* convert masks to lists - method wastes space, but is fast */
if (!mask) {
return null;
}
const list = new Int32Array(mask.length);
let elems = 0;
for (let i = 0, l = mask.length; i < l; i += 1) {

View File

@@ -5,51 +5,25 @@ Views on the annomatrix. all API here is defined in viewCreators.js and annoMat
*/
import clip from "../util/clip";
import AnnoMatrix from "./annoMatrix";
import { _whereCacheCreate, WhereCache } from "./whereCache";
import { _whereCacheCreate } from "./whereCache";
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 = (
field: Field,
colLabel: LabelType,
colSchema: ArraySchema,
colData: DataframeValueArray,
df: Dataframe
) => DataframeValueArray;
abstract class AnnoMatrixView extends AnnoMatrix {
constructor(viewOf: AnnoMatrix, rowIndex: LabelIndexBase | null = null) {
class AnnoMatrixView extends AnnoMatrix {
constructor(viewOf, rowIndex = null) {
const nObs = rowIndex ? rowIndex.size() : viewOf.nObs;
super(viewOf.schema, nObs, viewOf.nVar, rowIndex || viewOf.rowIndex);
this.viewOf = viewOf;
this.isView = true;
}
addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix {
addObsAnnoCategory(col, category) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.addObsAnnoCategory(col, category);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
return newAnnoMatrix;
}
async removeObsAnnoCategory(
col: LabelType,
category: string,
unassignedCategory: string
): Promise<AnnoMatrix> {
async removeObsAnnoCategory(col, category, unassignedCategory) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = await this.viewOf.removeObsAnnoCategory(
col,
@@ -60,7 +34,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix;
}
dropObsColumn(col: LabelType): AnnoMatrix {
dropObsColumn(col) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.dropObsColumn(col);
newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col);
@@ -68,29 +42,21 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix;
}
addObsColumn<T extends DataframeValueArray>(
colSchema: AnnotationColumnSchema,
Ctor: new (n: number) => T,
value: T
): AnnoMatrix {
addObsColumn(colSchema, Ctor, value) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.addObsColumn(colSchema, Ctor, value);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
return newAnnoMatrix;
}
renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix {
renameObsColumn(oldCol, newCol) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.renameObsColumn(oldCol, newCol);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
return newAnnoMatrix;
}
async setObsColumnValues(
col: LabelType,
rowLabels: Int32Array,
value: DataframeValue
): Promise<AnnoMatrix> {
async setObsColumnValues(col, rowLabels, value) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = await this.viewOf.setObsColumnValues(
col,
@@ -102,11 +68,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix;
}
async resetObsColumnValues<T extends DataframeValue>(
col: LabelType,
oldValue: T,
newValue: T
): Promise<AnnoMatrix> {
async resetObsColumnValues(col, oldValue, newValue) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = await this.viewOf.resetObsColumnValues(
col,
@@ -118,7 +80,7 @@ abstract class AnnoMatrixView extends AnnoMatrix {
return newAnnoMatrix;
}
addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix {
addEmbedding(colSchema) {
const newAnnoMatrix = this._clone();
newAnnoMatrix.viewOf = this.viewOf.addEmbedding(colSchema);
newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema;
@@ -127,32 +89,21 @@ abstract class AnnoMatrixView extends AnnoMatrix {
}
class AnnoMatrixMapView extends AnnoMatrixView {
mapFn: MapFn;
/*
A view which knows how to transform its data.
*/
constructor(viewOf: AnnoMatrix, mapFn: MapFn) {
constructor(viewOf, mapFn) {
super(viewOf);
this.mapFn = mapFn;
}
async _doLoad(
field: Field,
query: Query
): Promise<[WhereCache | null, Dataframe]> {
async _doLoad(field, query) {
const df = await this.viewOf._fetch(field, query);
const dfMapped = df.mapColumns(
(colData: DataframeValueArray, colIdx: number) => {
const dfMapped = df.mapColumns((colData, colIdx) => {
const colLabel = df.colIndex.getLabel(colIdx);
// @ts-expect-error ts-migrate --- TODO revisit:
// `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(
field,
query,
@@ -163,23 +114,12 @@ class AnnoMatrixMapView extends AnnoMatrixView {
}
export class AnnoMatrixClipView extends AnnoMatrixMapView {
clipRange: [number, number];
isClipped: boolean;
/*
A view which is a clipped transformation of its parent
*/
constructor(viewOf: AnnoMatrix, qmin: number, qmax: number) {
super(
viewOf,
(
field: Field,
colLabel: LabelType,
colSchema: ArraySchema,
colData: DataframeValueArray,
df: Dataframe
) => _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax)
constructor(viewOf, qmin, qmax) {
super(viewOf, (field, colLabel, colSchema, colData, df) =>
_clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax)
);
this.isClipped = true;
this.clipRange = [qmin, qmax];
@@ -191,19 +131,16 @@ export class AnnoMatrixRowSubsetView extends AnnoMatrixView {
/*
A view which is a subset of total rows.
*/
constructor(viewOf: AnnoMatrix, rowIndex: LabelIndexBase) {
constructor(viewOf, rowIndex) {
super(viewOf, rowIndex);
Object.seal(this);
}
async _doLoad(
field: Field,
query: Query
): Promise<[WhereCache | null, Dataframe]> {
async _doLoad(field, query) {
const df = await this.viewOf._fetch(field, query);
// don't try to row-subset the var dimension.
if (field === Field.var) {
if (field === "var") {
return [null, df];
}
@@ -221,23 +158,15 @@ export class AnnoMatrixRowSubsetView extends AnnoMatrixView {
Utility functions below
*/
function _clipAnnoMatrix(
field: Field,
colLabel: LabelType,
colSchema: ArraySchema,
colData: DataframeValueArray,
df: Dataframe,
qmin: number,
qmax: number
): DataframeValueArray {
function _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax) {
/* 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 (qmin < 0) qmin = 0;
if (qmax > 1) qmax = 1;
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 upper = quantiles[100 * qmax];
const clippedData = clip(colData.slice(), lower, upper, Number.NaN);

View File

@@ -2,7 +2,7 @@
Private support functions.
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.
There are three query forms:
@@ -49,33 +49,9 @@ creates a cache entry of:
}
*/
import { _getColumnDimensionNames } from "./schema";
import { _hashStringValues, Query } from "./query";
import { Field, Schema } from "../common/types/schema";
import { LabelArray } from "../util/dataframe/types";
import { _hashStringValues } from "./query";
export interface WhereCache {
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] {
export function _whereCacheGet(whereCache, schema, field, query) {
/*
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 ("where" in query) {
if (query.where) {
const {
field: queryField,
column: queryColumn,
@@ -92,7 +68,7 @@ export function _whereCacheGet(
const columnMap = whereCache?.where?.[field]?.[queryField];
return columnMap?.get(queryColumn)?.get(queryValue) ?? [undefined];
}
if ("summarize" in query) {
if (query.summarize) {
const {
method,
field: queryField,
@@ -109,17 +85,13 @@ export function _whereCacheGet(
return _getColumnDimensionNames(schema, field, query) ?? [undefined];
}
export function _whereCacheCreate(
field: Field,
query: Query,
columnLabels: LabelArray
): WhereCache | null {
export function _whereCacheCreate(field, query, columnLabels) {
/*
Create a new whereCache
*/
if (typeof query !== "object") return null;
if ("where" in query) {
if (query.where) {
const {
field: queryField,
column: queryColumn,
@@ -135,7 +107,7 @@ export function _whereCacheCreate(
},
};
}
if ("summarize" in query) {
if (query.summarize) {
const {
method,
field: queryField,
@@ -159,25 +131,20 @@ export function _whereCacheCreate(
return {};
}
function __mergeQueries(dst: WhereCacheTerms, src: WhereCacheTerms) {
function __mergeQueries(dst, src) {
for (const [queryField, columnMap] of Object.entries(src)) {
dst[queryField] = dst[queryField] || new Map();
for (const [queryColumn, valueMap] of columnMap) {
if (!dst[queryField].has(queryColumn))
dst[queryField].set(queryColumn, new Map());
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);
}
}
}
}
function __whereCacheMerge(
dst: WhereCache,
src: WhereCache | null
): WhereCache {
function __whereCacheMerge(dst, src) {
/*
merge src into dst (modifies dst)
*/
@@ -204,6 +171,6 @@ function __whereCacheMerge(
return dst;
}
export function _whereCacheMerge(...caches: (WhereCache | null)[]): WhereCache {
return caches.reduce(__whereCacheMerge, {} as WhereCache);
export function _whereCacheMerge(...caches) {
return caches.reduce(__whereCacheMerge, {});
}

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

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

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",
}

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;

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;

View File

@@ -15,38 +15,30 @@ import TermsOfServicePrompt from "./termsPrompt";
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) => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
loading: (state as any).controls.loading,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
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,
loading: state.controls.loading,
error: state.controls.error,
graphRenderCounter: state.controls.graphRenderCounter,
}))
class App extends React.Component {
// 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 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message
const { dispatch } = this.props;
/* listen for url changes, fire one when we start the app up */
window.addEventListener("popstate", this._onURLChanged);
this._onURLChanged();
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
dispatch(actions.doInitialDataLoad(window.location.search));
this.forceUpdate();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
_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;
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() {
// @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;
return (
<Container>
@@ -78,16 +70,13 @@ class App extends React.Component {
{loading || error ? null : (
<Layout>
<LeftSideBar />
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. */}
{(viewportRef: any) => (
{(viewportRef) => (
<>
<MenuBar />
<Embedding />
<Autosave />
<TermsOfServicePrompt />
{/* @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. */}
<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} />
</>
)}

View File

@@ -11,59 +11,42 @@ import {
Tooltip,
} 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) => ({
idhash:
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(state as any).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 as any).annotations,
// 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,
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
annotations: state.annotations,
auth: state.config?.authentication,
userInfo: state.userInfo,
writableCategoriesEnabled: state.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)
state.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<{}, State> {
// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS.
constructor(props: {}) {
class FilenameDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
filenameText: "",
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
dismissFilenameDialog = () => {};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
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 { filenameText } = this.state;
dispatch({
type: "set annotations collection name",
data: filenameText,
});
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
filenameError = () => {
const legalNames = /^\w+$/;
const { filenameText } = this.state;
let err = false;
if (filenameText === "") {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'.
err = "empty_string";
} else if (!legalNames.test(filenameText)) {
/*
@@ -72,17 +55,16 @@ class FilenameDialog extends React.Component<{}, State> {
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.
*/
// @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'.
err = "characters";
}
return err;
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
filenameErrorMessage = () => {
const err = this.filenameError();
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") {
markup = (
<span
@@ -96,7 +78,6 @@ class FilenameDialog extends React.Component<{}, State> {
Name cannot be blank
</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") {
markup = (
<span
@@ -114,21 +95,16 @@ class FilenameDialog extends React.Component<{}, State> {
return markup;
};
// 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 'annotations' does not exist on type 'Rea... Remove this comment to see the full error message
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,
// @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,
} = this.props;
const { filenameText } = this.state;
return (writableCategoriesEnabled || writableGenesetsEnabled) &&
annotations.promptForFilename &&
!annotations.dataCollectionNameIsReadOnly &&
@@ -152,7 +128,6 @@ class FilenameDialog extends React.Component<{}, State> {
<InputGroup
autoFocus
value={filenameText}
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
intent={this.filenameError(filenameText) ? "warning" : "none"}
onChange={(e) =>
this.setState({ filenameText: e.target.value })
@@ -163,14 +138,12 @@ class FilenameDialog extends React.Component<{}, State> {
<p
style={{
marginTop: 7,
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
visibility: this.filenameError(filenameText)
? "visible"
: "hidden",
color: Colors.ORANGE3,
}}
>
{/* @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1. */}
{this.filenameErrorMessage(filenameText)}
</p>
</div>
@@ -198,7 +171,6 @@ class FilenameDialog extends React.Component<{}, State> {
<Button onClick={this.dismissFilenameDialog}>Cancel</Button>
</Tooltip>
<Button
// @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.
disabled={!filenameText || this.filenameError(filenameText)}
onClick={this.handleCreateFilename}
intent="primary"

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;

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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -5,23 +5,14 @@ import * as globals from "../../globals";
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,
// @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,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onColorByClick' does not exist on type '... Remove this comment to see the full error message
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,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isScatterPlotX' does not exist on type '... Remove this comment to see the full error message
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,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'onScatterPlotXClick' does not exist on t... Remove this comment to see the full error message
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,
// @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,
}) => {
/*

View File

@@ -2,11 +2,9 @@ import React, { useEffect, useRef, useState } from "react";
import { interpolateCool } from "d3-scale-chromatic";
import * as d3 from "d3";
import { AxisDomain } from "d3";
import maybeScientific from "../../util/maybeScientific";
import clamp from "../../util/clamp";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
const Histogram = ({
field,
fieldForId,
@@ -19,8 +17,8 @@ const Histogram = ({
margin,
isColorBy,
selectionRange,
mini, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
}: any) => {
mini,
}) => {
const svgRef = useRef(null);
const [brush, setBrush] = useState(null);
@@ -71,19 +69,14 @@ const Histogram = ({
.data(bins)
.enter()
.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("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("height", (d) => y(0) - y(d))
.style(
"fill",
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
isColorBy
? // @ts-expect-error ts-migrate(6133) FIXME: 'd' is declared but its value is never read.
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
(d: any, i: any) => colorScale(histogramScale(binStart(i)))
? (d, i) => colorScale(histogramScale(binStart(i)))
: defaultBarColor
);
}
@@ -108,7 +101,6 @@ const Histogram = ({
const brushXselection = container
.insert("g")
.attr("class", "brush")
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
.attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`)
.call(brushX);
@@ -121,12 +113,7 @@ const Histogram = ({
d3
.axisBottom(x)
.ticks(4)
.tickFormat(
d3.format(maybeScientific(x)) as (
dv: AxisDomain,
i: number
) => string
)
.tickFormat(d3.format(maybeScientific(x)))
);
/* Y AXIS */
@@ -139,10 +126,8 @@ const Histogram = ({
.axisRight(y)
.ticks(3)
.tickFormat(
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
d3.format(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
y.domain().some((n: any) => Math.abs(n) >= 10000) ? ".0e" : ","
y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : ","
)
)
);
@@ -152,7 +137,6 @@ const Histogram = ({
svg.selectAll(".axis path").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 });
}
}, [histogram, isColorBy]);
@@ -162,7 +146,6 @@ const Histogram = ({
paint/update selection brush
*/
if (!brush) return;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'brushX' does not exist on type 'null'.
const { brushX, brushXselection } = brush;
const selection = d3.brushSelection(brushXselection.node());
if (!selectionRange && selection) {
@@ -179,9 +162,7 @@ const Histogram = ({
} else {
/* there is an active selection and a brush - make sure they match */
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]);
// @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]);
/*
only update the brush if it is grossly incorrect,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,41 +0,0 @@
import React from "react";
import { Button } from "@blueprintjs/core";
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 StillLoading = ({ zebra, displayName }: any) => (
/*
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