diff --git a/client/__tests__/util/annoMatrix/annoMatrix.test.ts b/client/__tests__/util/annoMatrix/annoMatrix.test.ts index 7deecd48a6..c8f786de7c 100644 --- a/client/__tests__/util/annoMatrix/annoMatrix.test.ts +++ b/client/__tests__/util/annoMatrix/annoMatrix.test.ts @@ -10,6 +10,7 @@ import { isubsetMask, } from "../../../src/annoMatrix"; import { Dataframe } from "../../../src/util/dataframe"; +import { Field } from "../../../src/common/types/schema"; enableFetchMocks(); @@ -34,7 +35,7 @@ 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).toBeUndefined(); + expect(annoMatrix.viewOf).toBe(annoMatrix); expect(annoMatrix.rowIndex).toBeDefined(); }); @@ -42,7 +43,7 @@ describe("AnnoMatrix", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (fetch as any).once(serverMocks.annotationsObs(["name_0"])); - const df = await annoMatrix.fetch("obs", "name_0"); + const df = await annoMatrix.fetch(Field.obs, "name_0"); expect(df).toBeInstanceOf(Dataframe); expect(df.colIndex.labels()).toEqual(["name_0"]); expect(df.dims).toEqual([annoMatrix.nObs, 1]); @@ -55,7 +56,7 @@ describe("AnnoMatrix", () => { .once(serverMocks.annotationsObs(["n_genes"])); await expect( - annoMatrix.fetch("obs", ["name_0", "n_genes"]) + annoMatrix.fetch(Field.obs, ["name_0", "n_genes"]) ).resolves.toBeInstanceOf(Dataframe); }); @@ -72,7 +73,7 @@ describe("AnnoMatrix", () => { ).resolves.toBeInstanceOf(Dataframe); }; - test("obs", async () => getLastTwo("obs")); + test(Field.obs, async () => getLastTwo(Field.obs)); test("var", async () => getLastTwo("var")); test("emb", async () => getLastTwo("emb")); }); @@ -81,15 +82,15 @@ describe("AnnoMatrix", () => { // 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("obs", "n_genes")).resolves.toBeInstanceOf( - Dataframe - ); + await expect( + annoMatrix.fetch(Field.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"])); await expect( - annoMatrix.fetch("obs", ["n_genes", "percent_mito"]) + annoMatrix.fetch(Field.obs, ["n_genes", "percent_mito"]) ).resolves.toBeInstanceOf(Dataframe); // more complex value filter query, enumerated @@ -151,9 +152,9 @@ describe("AnnoMatrix", () => { test("schema accessors", () => { expect(annoMatrix.getMatrixFields()).toEqual( - expect.arrayContaining(["X", "obs", "emb", "var"]) + expect.arrayContaining(["X", Field.obs, "emb", "var"]) ); - expect(annoMatrix.getMatrixColumns("obs")).toEqual( + expect(annoMatrix.getMatrixColumns(Field.obs)).toEqual( expect.arrayContaining(["name_0", "n_genes", "louvain"]) ); expect(annoMatrix.getColumnSchema("emb", "umap")).toEqual({ @@ -171,7 +172,7 @@ describe("AnnoMatrix", () => { test the mask & label access to subset via isubset and isubsetMask */ test("isubset", async () => { - const rowList = [0, 10]; + const rowList = new Int32Array([0, 10]); const rowMask = new Uint8Array(annoMatrix.nObs); for (let i = 0; i < rowList.length; i += 1) { rowMask[rowList[i]] = 1; @@ -188,8 +189,8 @@ describe("AnnoMatrix", () => { (fetch as any) .once(serverMocks.annotationsObs(["n_genes"])) .once(serverMocks.annotationsObs(["n_genes"])); - const ng1 = (await am1.fetch("obs", "n_genes")) as Dataframe; - const ng2 = (await am2.fetch("obs", "n_genes")) as Dataframe; + 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(); expect(ng1).toHaveLength(ng2.length); @@ -203,10 +204,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("obs")).not.toContain("foo"); + 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("obs", "foo")).rejects.toThrow( + await expect(base.fetch(Field.obs, "foo")).rejects.toThrow( "unknown column name" ); @@ -216,9 +217,9 @@ describe("AnnoMatrix", () => { Float32Array, 0 ); - expect(base.getMatrixColumns("obs")).not.toContain("foo"); - expect(am1.getMatrixColumns("obs")).toContain("foo"); - const foo: Dataframe = await am1.fetch("obs", "foo"); + 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(foo).toBeDefined(); expect(foo).toBeInstanceOf(Dataframe); expect(foo).toHaveLength(am1.nObs); @@ -228,11 +229,11 @@ describe("AnnoMatrix", () => { /* drop */ const am2 = am1.dropObsColumn("foo"); - expect(base.getMatrixColumns("obs")).not.toContain("foo"); - expect(am2.getMatrixColumns("obs")).not.toContain("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("obs", "foo")).rejects.toThrow( + await expect(am2.fetch(Field.obs, "foo")).rejects.toThrow( "unknown column name" ); } @@ -245,10 +246,10 @@ describe("AnnoMatrix", () => { const am1 = clip(annoMatrix, 0.1, 0.9); await addDrop(am1); - const am2 = isubset(am1, [0, 1, 2, 20, 30, 400]); + const am2 = isubset(am1, new Int32Array([0, 1, 2, 20, 30, 400])); await addDrop(am2); - const am3 = isubset(annoMatrix, [10, 0, 7, 3]); + const am3 = isubset(annoMatrix, new Int32Array([10, 0, 7, 3])); await addDrop(am3); const am4 = clip(am3, 0, 1); @@ -257,10 +258,10 @@ describe("AnnoMatrix", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (fetch as any).mockResponse(serverMocks.responder); - await am1.fetch("obs", am1.getMatrixColumns("obs")); - await am2.fetch("obs", am2.getMatrixColumns("obs")); - await am3.fetch("obs", am3.getMatrixColumns("obs")); - await am4.fetch("obs", am4.getMatrixColumns("obs")); + 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)); // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (fetch as any).resetMocks(); @@ -287,7 +288,7 @@ describe("AnnoMatrix", () => { "unassigned" ); - const testVal = await am.fetch("obs", "test"); + const testVal = await am.fetch(Field.obs, "test"); expect(testVal.col("test").asArray()).toEqual( new Array(am.nObs).fill("unassigned") ); @@ -295,7 +296,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("obs", "test"); + const testVal1 = await am1.fetch(Field.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]); @@ -303,8 +304,8 @@ describe("AnnoMatrix", () => { } expect(testVal1).not.toBe(testVal); expect(testVal1.col("test").asArray()).toEqual(expt); - expect(am1.getColumnSchema("obs", "test").type).toBe("categorical"); - expect(am1.getColumnSchema("obs", "test").categories).toEqual( + expect(am1.getColumnSchema(Field.obs, "test").type).toBe("categorical"); + expect(am1.getColumnSchema(Field.obs, "test").categories).toEqual( expect.arrayContaining(["unassigned", "red", "green", "yo"]) ); @@ -312,7 +313,7 @@ describe("AnnoMatrix", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (fetch as any).mockRejectOnce(new Error("unknown column name")); am = am1.dropObsColumn("test"); - await expect(am.fetch("obs", "test")).rejects.toThrow( + await expect(am.fetch(Field.obs, "test")).rejects.toThrow( "unknown column name" ); } @@ -325,18 +326,18 @@ describe("AnnoMatrix", () => { const am1 = clip(annoMatrix, 0.1, 0.9); await addSetDrop(am1); - const am2 = isubset(am1, [0, 1, 2, 10, 20, 30, 400]); + const am2 = isubset(am1, new Int32Array([0, 1, 2, 10, 20, 30, 400])); await addSetDrop(am2); - const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]); + const am3 = isubset(annoMatrix, new Int32Array([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); - await am1.fetch("obs", am1.getMatrixColumns("obs")); - await am2.fetch("obs", am2.getMatrixColumns("obs")); - await am3.fetch("obs", am3.getMatrixColumns("obs")); + 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 addSetDrop(am1); await addSetDrop(am2); diff --git a/client/__tests__/util/annoMatrix/crossfilter.test.ts b/client/__tests__/util/annoMatrix/crossfilter.test.ts index 27cdcbe582..4d3d636495 100644 --- a/client/__tests__/util/annoMatrix/crossfilter.test.ts +++ b/client/__tests__/util/annoMatrix/crossfilter.test.ts @@ -14,6 +14,7 @@ import { } from "../../../src/annoMatrix"; import { Dataframe } from "../../../src/util/dataframe"; import { rangeFill } from "../../../src/util/range"; +import { Field, Schema } from "../../../src/common/types/schema"; enableFetchMocks(); @@ -29,7 +30,7 @@ describe("AnnoMatrixCrossfilter", () => { // reset all fetch mocking state annoMatrix = new AnnoMatrixLoader( serverMocks.baseDataURL, - serverMocks.schema.schema + serverMocks.schema.schema as Schema ); crossfilter = new AnnoMatrixObsCrossfilter(annoMatrix); }); @@ -76,7 +77,7 @@ describe("AnnoMatrixCrossfilter", () => { (fetch as any).once( serverMocks.dataframeResponse(["louvain"], [obsLouvain]) ); - let newCrossfilter = await crossfilter.select("obs", "louvain", { + let newCrossfilter = await crossfilter.select(Field.obs, "louvain", { mode: "none", }); @@ -87,7 +88,7 @@ describe("AnnoMatrixCrossfilter", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. expect((fetch as any).mock.calls).toHaveLength(1); - newCrossfilter = await crossfilter.select("obs", "louvain", { + newCrossfilter = await crossfilter.select(Field.obs, "louvain", { mode: "all", }); expect(newCrossfilter.countSelected()).toEqual(annoMatrix.nObs); @@ -100,7 +101,7 @@ describe("AnnoMatrixCrossfilter", () => { (fetch as any).once( serverMocks.dataframeResponse(["louvain"], [obsLouvain]) ); - xfltr = await crossfilter.select("obs", "louvain", { + xfltr = await crossfilter.select(Field.obs, "louvain", { mode: "exact", values: ["NK cells", "B cells"], }); @@ -133,7 +134,7 @@ describe("AnnoMatrixCrossfilter", () => { ) ); - const df: Dataframe = await annoMatrix.fetch("obs", "louvain"); + const df: Dataframe = await annoMatrix.fetch(Field.obs, "louvain"); const values = df.col("louvain").asArray(); const selected = xfltr.allSelectedMask(); values.every( @@ -146,7 +147,7 @@ describe("AnnoMatrixCrossfilter", () => { (fetch as any).once( serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)]) ); - xfltr = await xfltr.select("obs", "n_genes", { + xfltr = await xfltr.select(Field.obs, "n_genes", { mode: "range", lo: 0, hi: 500, @@ -258,7 +259,7 @@ describe("AnnoMatrixCrossfilter", () => { (fetch as any).once( serverMocks.dataframeResponse(["louvain"], [obsLouvain]) ); - xfltr = await xfltr.select("obs", "louvain", { + xfltr = await xfltr.select(Field.obs, "louvain", { mode: "exact", values: ["NK cells", "B cells"], }); @@ -267,7 +268,7 @@ describe("AnnoMatrixCrossfilter", () => { expect(xfltr.countSelected()).toEqual(240); const df: Dataframe = (await annoMatrixSubset.fetch( - "obs", + Field.obs, "louvain" )) as Dataframe; expect(df).toBeDefined(); @@ -291,7 +292,7 @@ describe("AnnoMatrixCrossfilter", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (fetch as any).mockRejectOnce(new Error("unknown column name")); - await expect(crossfilter.select("obs", "foo")).rejects.toThrow( + await expect(crossfilter.select(Field.obs, "foo")).rejects.toThrow( "unknown column name" ); }); @@ -304,7 +305,7 @@ describe("AnnoMatrixCrossfilter", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. async function helperAddTestCol(cf: any, colName: any, colSchema = null) { expect( - cf.annoMatrix.getMatrixColumns("obs").includes(colName) + cf.annoMatrix.getMatrixColumns(Field.obs).includes(colName) ).toBeFalsy(); if (colSchema === null) { @@ -326,7 +327,7 @@ describe("AnnoMatrixCrossfilter", () => { (v: any) => v.name === colName ) ).toHaveLength(1); - const df = await xfltr.annoMatrix.fetch("obs", colName); + const df = await xfltr.annoMatrix.fetch(Field.obs, colName); expect(df.hasCol(colName)).toBeTruthy(); return xfltr; } @@ -334,7 +335,7 @@ describe("AnnoMatrixCrossfilter", () => { test("addObsColumn", async () => { expect(crossfilter.countSelected()).toBe(annoMatrix.nObs); expect( - crossfilter.annoMatrix.getMatrixColumns("obs").includes("foo") + crossfilter.annoMatrix.getMatrixColumns(Field.obs).includes("foo") ).toBeFalsy(); const xfltr = crossfilter.addObsColumn( { name: "foo", type: "categorical", categories: ["A"] }, @@ -345,7 +346,7 @@ describe("AnnoMatrixCrossfilter", () => { // check schema updates correctly. expect(xfltr.countSelected()).toBe(annoMatrix.nObs); expect( - xfltr.annoMatrix.getMatrixColumns("obs").includes("foo") + xfltr.annoMatrix.getMatrixColumns(Field.obs).includes("foo") ).toBeTruthy(); expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toMatchObject({ name: "foo", @@ -359,7 +360,7 @@ describe("AnnoMatrixCrossfilter", () => { ).toHaveLength(1); // check data update. - const df: Dataframe = await xfltr.annoMatrix.fetch("obs", "foo"); + const df: Dataframe = await xfltr.annoMatrix.fetch(Field.obs, "foo"); expect( df .col("foo") @@ -408,13 +409,13 @@ describe("AnnoMatrixCrossfilter", () => { 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("obs", "foo")).rejects.toThrow( + await expect(xfltr.annoMatrix.fetch(Field.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("obs", "bar", { + xfltr = await xfltr.select(Field.obs, "bar", { mode: "exact", values: "whatever", }); @@ -422,9 +423,9 @@ describe("AnnoMatrixCrossfilter", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (fetch as any).mockRejectOnce(new Error("unknown column name")); - await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow( - "unknown column name" - ); + await expect( + xfltr.select(Field.obs, "bar", { mode: "all" }) + ).rejects.toThrow("unknown column name"); }); test("renameObsColumn", async () => { @@ -441,23 +442,25 @@ describe("AnnoMatrixCrossfilter", () => { // add a column, then rename it. xfltr = await helperAddTestCol(crossfilter, "foo"); xfltr = xfltr.renameObsColumn("foo", "bar"); - expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toBeUndefined(); - expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({ + expect( + xfltr.annoMatrix.getColumnSchema(Field.obs, "foo") + ).toBeUndefined(); + expect(xfltr.annoMatrix.getColumnSchema(Field.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("obs", "foo")).rejects.toThrow( + await expect(xfltr.annoMatrix.fetch(Field.obs, "foo")).rejects.toThrow( "unknown column name" ); - const df = await xfltr.annoMatrix.fetch("obs", "bar"); + const df = await xfltr.annoMatrix.fetch(Field.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("obs", "bar", { + xfltr = await xfltr.select(Field.obs, "bar", { mode: "exact", values: "whatever", }); @@ -465,11 +468,11 @@ describe("AnnoMatrixCrossfilter", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. (fetch as any).mockRejectOnce(new Error("unknown column name")); - await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow( - "unknown column name" - ); await expect( - xfltr.select("obs", "xyz", { mode: "none" }) + xfltr.select(Field.obs, "bar", { mode: "all" }) + ).rejects.toThrow("unknown column name"); + await expect( + xfltr.select(Field.obs, "xyz", { mode: "none" }) ).resolves.toBeInstanceOf(AnnoMatrixObsCrossfilter); }); @@ -493,7 +496,7 @@ describe("AnnoMatrixCrossfilter", () => { categories: ["unassigned"], }); xfltr = xfltr.addObsAnnoCategory("foo", "a-new-label"); - expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject({ name: "foo", type: "categorical", categories: expect.arrayContaining(["a-new-label", "unassigned"]), @@ -511,12 +514,12 @@ describe("AnnoMatrixCrossfilter", () => { type: "categorical", categories: ["unassigned"], }); - xfltr = await xfltr.select("obs", "bar", { + xfltr = await xfltr.select(Field.obs, "bar", { mode: "exact", values: "something", }); xfltr = xfltr.addObsAnnoCategory("bar", "a-new-label"); - expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({ + expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "bar")).toMatchObject({ name: "bar", type: "categorical", categories: expect.arrayContaining(["a-new-label", "unassigned"]), @@ -540,14 +543,14 @@ describe("AnnoMatrixCrossfilter", () => { type: "categorical", categories: ["unassigned", "red", "green", "blue"], }); - xfltr = await xfltr.select("obs", "foo", { mode: "all" }); + xfltr = await xfltr.select(Field.obs, "foo", { mode: "all" }); expect( - (await xfltr.annoMatrix.fetch("obs", "foo")) + (await xfltr.annoMatrix.fetch(Field.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") ).toBeTruthy(); - expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + expect(xfltr.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject({ name: "foo", type: "categorical", categories: expect.arrayContaining([ @@ -561,21 +564,23 @@ describe("AnnoMatrixCrossfilter", () => { // remove an unused category const xfltr1 = await xfltr.removeObsAnnoCategory("foo", "red", "mumble"); expect( - (await xfltr1.annoMatrix.fetch("obs", "foo")) + (await xfltr1.annoMatrix.fetch(Field.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") ).toBeTruthy(); - expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ - name: "foo", - type: "categorical", - categories: expect.arrayContaining([ - "unassigned", - "green", - "blue", - "mumble", - ]), - }); + expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( + { + name: "foo", + type: "categorical", + categories: expect.arrayContaining([ + "unassigned", + "green", + "blue", + "mumble", + ]), + } + ); // remove a used category const xfltr2 = await xfltr.removeObsAnnoCategory( @@ -584,16 +589,18 @@ describe("AnnoMatrixCrossfilter", () => { "red" ); expect( - (await xfltr2.annoMatrix.fetch("obs", "foo")) + (await xfltr2.annoMatrix.fetch(Field.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") ).toBeTruthy(); - expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ - name: "foo", - type: "categorical", - categories: expect.arrayContaining(["green", "blue", "red"]), - }); + expect(xfltr2.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( + { + name: "foo", + type: "categorical", + categories: expect.arrayContaining(["green", "blue", "red"]), + } + ); }); test("setObsColumnValues", async () => { @@ -611,7 +618,7 @@ describe("AnnoMatrixCrossfilter", () => { type: "categorical", categories: ["unassigned", "red", "green", "blue"], }); - xfltr = await xfltr.select("obs", "foo", { mode: "all" }); + xfltr = await xfltr.select(Field.obs, "foo", { mode: "all" }); // catch unknown row label await expect(() => @@ -620,14 +627,14 @@ describe("AnnoMatrixCrossfilter", () => { // set a few rows expect( - (await xfltr.annoMatrix.fetch("obs", "foo")) + (await xfltr.annoMatrix.fetch(Field.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") ).toBeTruthy(); const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple"); expect( - (await xfltr1.annoMatrix.fetch("obs", "foo")) + (await xfltr1.annoMatrix.fetch(Field.obs, "foo")) .col("foo") .asArray() .every( @@ -636,20 +643,22 @@ describe("AnnoMatrixCrossfilter", () => { v === "unassigned" || (v === "purple" && (i === 0 || i === 10)) ) ).toBeTruthy(); - expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ - name: "foo", - type: "categorical", - categories: expect.arrayContaining([ - "unassigned", - "red", - "green", - "blue", - "purple", - ]), - }); + expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( + { + name: "foo", + type: "categorical", + categories: expect.arrayContaining([ + "unassigned", + "red", + "green", + "blue", + "purple", + ]), + } + ); expect(xfltr1.countSelected()).toEqual(xfltr1.annoMatrix.nObs); - const xfltr2 = await xfltr1.select("obs", "foo", { + const xfltr2 = await xfltr1.select(Field.obs, "foo", { mode: "exact", values: ["purple"], }); @@ -672,7 +681,7 @@ describe("AnnoMatrixCrossfilter", () => { type: "categorical", categories: ["unassigned", "red", "green", "blue"], }); - xfltr = await xfltr.select("obs", "foo", { + xfltr = await xfltr.select(Field.obs, "foo", { mode: "exact", values: "red", }); @@ -683,12 +692,12 @@ describe("AnnoMatrixCrossfilter", () => { ).rejects.toThrow("unknown category"); let xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple"); - xfltr1 = await xfltr1.select("obs", "foo", { + xfltr1 = await xfltr1.select(Field.obs, "foo", { mode: "exact", values: "purple", }); expect( - (await xfltr1.annoMatrix.fetch("obs", "foo")) + (await xfltr1.annoMatrix.fetch(Field.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") @@ -696,29 +705,31 @@ describe("AnnoMatrixCrossfilter", () => { xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta"); expect( - (await xfltr1.annoMatrix.fetch("obs", "foo")) + (await xfltr1.annoMatrix.fetch(Field.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") ).toHaveLength(2); expect( - (await xfltr1.annoMatrix.fetch("obs", "foo")) + (await xfltr1.annoMatrix.fetch(Field.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") ).toHaveLength(0); - expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ - name: "foo", - type: "categorical", - categories: expect.arrayContaining([ - "unassigned", - "red", - "green", - "blue", - "purple", - "magenta", - ]), - }); + expect(xfltr1.annoMatrix.getColumnSchema(Field.obs, "foo")).toMatchObject( + { + name: "foo", + type: "categorical", + categories: expect.arrayContaining([ + "unassigned", + "red", + "green", + "blue", + "purple", + "magenta", + ]), + } + ); }); }); @@ -729,7 +740,7 @@ describe("AnnoMatrixCrossfilter", () => { (fetch as any).once( serverMocks.dataframeResponse(["louvain"], [obsLouvain]) ); - const xfltr = await crossfilter.select("obs", "louvain", { + const xfltr = await crossfilter.select(Field.obs, "louvain", { mode: "exact", values: "B cells", }); diff --git a/client/__tests__/util/annoMatrix/whereCache.test.ts b/client/__tests__/util/annoMatrix/whereCache.test.ts index 640ac5236a..69eca4580f 100644 --- a/client/__tests__/util/annoMatrix/whereCache.test.ts +++ b/client/__tests__/util/annoMatrix/whereCache.test.ts @@ -4,31 +4,34 @@ import { _whereCacheCreate, _whereCacheMerge, } from "../../../src/annoMatrix/whereCache"; +import { Field, Schema } from "../../../src/common/types/schema"; +import { Query } from "../../../src/annoMatrix/query"; -const schema = {}; +const schema = {} as Schema; describe("whereCache", () => { test("whereCacheGet - where query, missing cache values", () => { expect( - _whereCacheGet({}, schema, "X", { + _whereCacheGet({}, schema, Field.X, { where: { - field: "var", + field: Field.var, column: "foo", value: "bar", }, }) ).toEqual([undefined]); expect( - _whereCacheGet({}, schema, "X", { + _whereCacheGet({}, schema, Field.X, { summarize: { - field: "var", + method: "mean", + field: Field.var, column: "foo", values: ["bar"], }, }) ).toEqual([undefined]); expect( - _whereCacheGet({ where: { X: {} } }, schema, "X", { + _whereCacheGet({ where: { X: {} } }, schema, Field.X, { where: { field: "var", column: "foo", @@ -37,7 +40,7 @@ describe("whereCache", () => { }) ).toEqual([undefined]); expect( - _whereCacheGet({ where: { X: { var: new Map() } } }, schema, "X", { + _whereCacheGet({ where: { X: { var: new Map() } } }, schema, Field.X, { where: { field: "var", column: "foo", @@ -49,7 +52,7 @@ describe("whereCache", () => { _whereCacheGet( { where: { X: { var: new Map([["foo", new Map()]]) } } }, schema, - "X", + Field.X, { where: { field: "var", @@ -63,7 +66,7 @@ describe("whereCache", () => { test("whereCacheGet - summarize query, missing cache values", () => { expect( - _whereCacheGet({}, schema, "X", { + _whereCacheGet({}, schema, Field.X, { summarize: { method: "mean", field: "var", @@ -76,7 +79,7 @@ describe("whereCache", () => { _whereCacheGet( { summarize: { X: { mean: { var: new Map() } } } }, schema, - "X", + Field.X, { summarize: { method: "mean", @@ -122,7 +125,7 @@ describe("whereCache", () => { }; expect( - _whereCacheGet(whereCache, schema, "X", { + _whereCacheGet(whereCache, schema, Field.X, { where: { field: "var", column: "foo", @@ -131,7 +134,7 @@ describe("whereCache", () => { }) ).toEqual([0]); expect( - _whereCacheGet(whereCache, schema, "X", { + _whereCacheGet(whereCache, schema, Field.X, { summarize: { method: "mean", field: "var", @@ -141,7 +144,7 @@ describe("whereCache", () => { }) ).toEqual([0]); expect( - _whereCacheGet(whereCache, schema, "X", { + _whereCacheGet(whereCache, schema, Field.X, { where: { field: "var", column: "foo", @@ -150,7 +153,7 @@ describe("whereCache", () => { }) ).toEqual([1, 2]); expect( - _whereCacheGet(whereCache, schema, "X", { + _whereCacheGet(whereCache, schema, Field.X, { summarize: { method: "mean", field: "var", @@ -159,9 +162,12 @@ describe("whereCache", () => { }, }) ).toEqual([1, 2]); - expect(_whereCacheGet(whereCache, schema, "Y", {})).toEqual([undefined]); expect( - _whereCacheGet(whereCache, schema, "X", { + // Force invalid field value Y + _whereCacheGet(whereCache, schema, "Y" as Field, {} as Query) + ).toEqual([undefined]); + expect( + _whereCacheGet(whereCache, schema, Field.X, { where: { field: "whoknows", column: "whatever", @@ -170,7 +176,7 @@ describe("whereCache", () => { }) ).toEqual([undefined]); expect( - _whereCacheGet(whereCache, schema, "X", { + _whereCacheGet(whereCache, schema, Field.X, { where: { field: "var", column: "whatever", @@ -179,7 +185,7 @@ describe("whereCache", () => { }) ).toEqual([undefined]); expect( - _whereCacheGet(whereCache, schema, "X", { + _whereCacheGet(whereCache, schema, Field.X, { where: { field: "var", column: "foo", @@ -198,7 +204,7 @@ describe("whereCache", () => { }, }; const wc = _whereCacheCreate( - "field", + Field.obs, { where: { field: "queryField", @@ -212,7 +218,7 @@ describe("whereCache", () => { expect(wc).toEqual( expect.objectContaining({ where: { - field: { + [Field.obs]: { queryField: expect.any(Map), }, }, @@ -220,18 +226,19 @@ describe("whereCache", () => { ); // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - expect((wc.where as any).field.queryField.has("queryColumn")).toEqual(true); + expect((wc.where as any)[Field.obs].queryField.has("queryColumn")).toEqual(true); expect( // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (wc.where as any).field.queryField.get("queryColumn") + (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.queryField.get("queryColumn").has("queryValue") + (wc.where as any)[Field.obs].queryField.get("queryColumn").has("queryValue") ).toEqual(true); - expect(_whereCacheGet(wc, schema, "field", query)).toEqual([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]); }); test("whereCacheCreate, summarize query", () => { @@ -243,12 +250,14 @@ describe("whereCache", () => { values: ["queryValue"], }, }; - const wc = _whereCacheCreate("field", query, [0, 1, 2]); - expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]); + 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]); }); test("whereCacheCreate, unknown query type", () => { - expect(_whereCacheCreate("field", { foobar: true }, [1])).toEqual({}); + // @ts-expect-error --- force invalid query {foobar: true} + expect(_whereCacheCreate(Field.obs, { foobar: true }, [1])).toEqual({}); }); test("whereCacheMerge, where queries", () => { @@ -256,19 +265,20 @@ describe("whereCache", () => { // remember, will mutate dst const src = _whereCacheCreate( - "field", + Field.obs, { where: { field: "queryField", column: "queryColumn", value: "foo" } }, ["foo"] ); const dst1 = _whereCacheCreate( - "field", + Field.obs, { where: { field: "queryField", column: "queryColumn", value: "bar" } }, ["dst1"] ); - wc = _whereCacheMerge(dst1, src); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert dst1 and src to be non-null + wc = _whereCacheMerge(dst1!, src!); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { where: { field: "queryField", column: "queryColumn", @@ -277,7 +287,7 @@ describe("whereCache", () => { }) ).toEqual(["foo"]); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { where: { field: "queryField", column: "queryColumn", @@ -287,13 +297,14 @@ describe("whereCache", () => { ).toEqual(["dst1"]); const dst2 = _whereCacheCreate( - "field", + Field.obs, { where: { field: "queryField", column: "queryColumn", value: "bar" } }, ["dst2"] ); - wc = _whereCacheMerge(dst2, dst1, src); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert dst2m, dst1 and src to be non-null + wc = _whereCacheMerge(dst2!, dst1!, src!); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { where: { field: "queryField", column: "queryColumn", @@ -302,7 +313,7 @@ describe("whereCache", () => { }) ).toEqual(["foo"]); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { where: { field: "queryField", column: "queryColumn", @@ -311,17 +322,20 @@ describe("whereCache", () => { }) ).toEqual(["dst1"]); - wc = _whereCacheMerge({}, src); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert src to be non-null + wc = _whereCacheMerge({}, src!); expect(wc).toEqual(src); - wc = _whereCacheMerge({ where: { field: { queryField: new Map() } } }, 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!); 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", + Field.obs, { where: { field: "queryField", @@ -330,9 +344,10 @@ describe("whereCache", () => { }, }, ["a"] - ), + )!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion --- assert wc to be non-null _whereCacheCreate( - "field", + Field.obs, { summarize: { method: "mean", @@ -342,11 +357,11 @@ describe("whereCache", () => { }, }, ["b"] - ) + )! ); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { where: { field: "queryField", column: "queryColumn", @@ -356,7 +371,7 @@ describe("whereCache", () => { ).toEqual(["a"]); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { summarize: { method: "mean", field: "queryField", @@ -367,7 +382,7 @@ describe("whereCache", () => { ).toEqual(["b"]); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { where: { field: "queryField", column: "queryColumn", @@ -377,7 +392,7 @@ describe("whereCache", () => { ).toEqual([undefined]); expect( - _whereCacheGet(wc, schema, "field", { + _whereCacheGet(wc, schema, Field.obs, { summarize: { method: "no-such-method", field: "queryField", diff --git a/client/src/actions/embedding.ts b/client/src/actions/embedding.ts index d966a30e24..9135ceeb4c 100644 --- a/client/src/actions/embedding.ts +++ b/client/src/actions/embedding.ts @@ -7,6 +7,7 @@ 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( @@ -26,7 +27,7 @@ export async function _switchEmbedding( const obsCrossfilter = await new AnnoMatrixObsCrossfilter( annoMatrix, prevCrossfilter.obsCrossfilter - ).select("emb", newEmbeddingName, { + ).select(Field.emb, newEmbeddingName, { mode: "all", }); return [annoMatrix, obsCrossfilter]; diff --git a/client/src/annoMatrix/annoMatrix.ts b/client/src/annoMatrix/annoMatrix.ts index 8f3bc2a31f..1c2765a305 100644 --- a/client/src/annoMatrix/annoMatrix.ts +++ b/client/src/annoMatrix/annoMatrix.ts @@ -2,6 +2,9 @@ import { Dataframe, IdentityInt32Index, dataframeMemo, + LabelType, + DataframeValue, + DataframeValueArray, } from "../util/dataframe"; import { _getColumnDimensionNames, @@ -10,45 +13,70 @@ import { _getWritableColumns, } from "./schema"; import { indexEntireSchema } from "../util/stateManager/schemaHelpers"; -import { _whereCacheGet, _whereCacheMerge } from "./whereCache"; +import { + _whereCacheGet, + _whereCacheMerge, + WhereCache, + WhereCacheColumnLabels, +} from "./whereCache"; import _shallowClone from "./clone"; -import { _queryValidate, _queryCacheKey } from "./query"; +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"; const _dataframeCache = dataframeMemo(128); -export default class AnnoMatrix { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - public isView: any; +interface Cache { + [Field.obs]: Dataframe; + [Field.var]: Dataframe; + [Field.emb]: Dataframe; + [Field.X]: Dataframe; +} + +interface PendingLoad { + [Field.obs]: { [key: string]: Promise }; + [Field.var]: { [key: string]: Promise }; + [Field.emb]: { [key: string]: Promise }; + [Field.X]: { [key: string]: Promise }; +} + +export interface UserFlags { + isUserSubsetView?: boolean; + isEmbSubsetView?: boolean; +} + +export default abstract class AnnoMatrix { + public isView: boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - public nObs: any; + public nObs: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - public nVar: any; + public nVar: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - public rowIndex: any; + public rowIndex: LabelIndexBase; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - public schema: any; + public schema: Schema; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - public userFlags: any; + public userFlags: UserFlags; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - public viewOf: any; + public viewOf: AnnoMatrix; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - protected _cache: any; + public _cache: Cache; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - private _pendingLoad: any; + private _pendingLoad: PendingLoad; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - private _whereCache: any; + private _whereCache: WhereCache; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - private _gcInfo: any; + private _gcInfo: Map; /* Abstract base class for all AnnoMatrix objects. This class provides a proxy @@ -80,16 +108,19 @@ export default class AnnoMatrix { subset(annoMatrix, rowLabels) -> annoMatrix etc. */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - static fields() { + static fields(): Field[] { /* return the fields present in the AnnoMatrix instance. */ - return ["obs", "var", "emb", "X"]; + return [Field.obs, Field.var, Field.emb, Field.X]; } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - constructor(schema: any, nObs: any, nVar: any, rowIndex = null) { + constructor( + schema: RawSchema, + nObs: number, + nVar: number, + rowIndex: LabelIndexBase | null = null + ) { /* Private constructor - this is an abstract base class. Do not use. */ @@ -105,7 +136,7 @@ export default 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, undefined/null if not a view. + * viewOf - pointer to parent annomatrix if a view, self 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. */ @@ -114,7 +145,7 @@ export default class AnnoMatrix { this.nVar = nVar; this.rowIndex = rowIndex || new IdentityInt32Index(nObs); this.isView = false; - this.viewOf = undefined; + this.viewOf = this; this.userFlags = {}; /* @@ -137,16 +168,14 @@ export default class AnnoMatrix { emb: {}, X: {}, }; - this._whereCache = {}; + this._whereCache = {} as WhereCache; this._gcInfo = new Map(); } /** ** Schema helper/accessors **/ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - getMatrixColumns(field) { + getMatrixColumns(field: Field): string[] { /* Return array of column names in the field. ONLY supported on the obs, var and emb fields. X currently unimplemented and will throw. @@ -158,8 +187,8 @@ export default class AnnoMatrix { return _schemaColumns(this.schema, field); } - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types -- need to be able to call this on instances - getMatrixFields() { + // eslint-disable-next-line class-methods-use-this -- need to be able to call this on instances + getMatrixFields(): Field[] { /* Return array of fields in this annoMatrix. Currently hard-wired to return: ["X", "obs", "var", "emb"]. @@ -169,9 +198,7 @@ export default class AnnoMatrix { return AnnoMatrix.fields(); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - getColumnSchema(field, col) { + getColumnSchema(field: Field, col: LabelType): ArraySchema { /* Return the schema for the field & column ,eg, @@ -183,9 +210,7 @@ export default class AnnoMatrix { return _getColumnSchema(this.schema, field, col); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - getColumnDimensions(field, col) { + getColumnDimensions(field: Field, col: LabelType): LabelArray | undefined { /* 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, @@ -203,23 +228,19 @@ export default class AnnoMatrix { /** ** General utility methods **/ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - base() { + base(): AnnoMatrix { /* return the base of view, or `this` if not a view. */ - // eslint-disable-next-line @typescript-eslint/no-this-alias --- FIXME: disabled temporarily on migrate to TS. - let annoMatrix = this; - while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf; + let annoMatrix = this._getViewOf(); + while (annoMatrix.isView) annoMatrix = annoMatrix._getViewOf(); return annoMatrix; } /** ** Load / read interfaces **/ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - fetch(field, q): Dataframe { + fetch(field: Field, q: Query | Query[]): Promise { /* Return the given query on a single matrix field as a single dataframe. Currently supports ONLY full column query. @@ -276,9 +297,7 @@ export default class AnnoMatrix { return this._fetch(field, q); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - prefetch(field, q): void { + prefetch(field: Field, q: Query): void { /* Start a data fetch & cache fill. Identical to fetch() except it does not return a value. @@ -307,163 +326,147 @@ export default class AnnoMatrix { ** The actual implementation is in the sub-classes, which MUST override these. **/ - // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read. - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - addObsAnnoCategory(col, category) { - /* - Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix. - Typical use is to add a new user-created label to a user-created obs categorical - annotation. - - Will throw column does not exist or is not writable. - - Example: - - addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix - - */ - _subclassResponsibility(); - } + /* + Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix. + Typical use is to add a new user-created label to a user-created obs categorical + annotation. - // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read. - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - async removeObsAnnoCategory(col, category, unassignedCategory) { - /* - Remove a category value from an obs column, reassign any obs having that value - to the 'unassignedCategory' value, and return a promise for a new AnnoMatrix. - Typical use is to remove a user-created label from a user-created obs categorical - annotation. + Will throw column does not exist or is not writable. - Will throw column does not exist or is not writable. + Example: - An `unassignedCategory` value must be provided, for assignment to any obs/cells - that had the now-delete category label as their value. + addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix - Example: - await removeObsAnnoCategory("my-tissue-type", "right earlobe", "unassigned") -> AnnoMatrix + */ + abstract addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix; - NOTE: method is async as it may need to fetch data to provide the reassignment. - */ - _subclassResponsibility(); - } + /* + Remove a category value from an obs column, reassign any obs having that value + to the 'unassignedCategory' value, and return a promise for a new AnnoMatrix. + Typical use is to remove a user-created label from a user-created obs categorical + annotation. - // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read. - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - dropObsColumn(col) { - /* - Drop an entire writable column, eg a user-created obs annotation. Typical use - is to provide the "Delete Category" implementation. Returns the new AnnoMatrix. - Will throw if not a writable annotation. + Will throw column does not exist or is not writable. - Will throw column does not exist or is not writable. + An `unassignedCategory` value must be provided, for assignment to any obs/cells + that had the now-delete category label as their value. - Example: + Example: + await removeObsAnnoCategory("my-tissue-type", "right earlobe", "unassigned") -> AnnoMatrix - dropObsColumn("old annotations") -> AnnoMatrix - */ - _subclassResponsibility(); - } + NOTE: method is async as it may need to fetch data to provide the reassignment. + */ + abstract removeObsAnnoCategory( + col: LabelType, + category: Category, + unassignedCategory: string + ): Promise; - // @ts-expect-error ts-migrate(6133) FIXME: 'colSchema' is declared but its value is never rea... Remove this comment to see the full error message - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - addObsColumn(colSchema, Ctor, value) { - /* - Add a new writable OBS annotation column, with the caller-specified schema, initial value - type and value. + /* + Drop an entire writable column, eg a user-created obs annotation. Typical use + is to provide the "Delete Category" implementation. Returns the new AnnoMatrix. + Will throw if not a writable annotation. - Value may be any one of: - * an array of values - * a primitive type, including null or undefined. - If an array, length must be the same as 'this.nObs', and constructor must equal 'Ctor'. - If a primitive, 'Ctor' will be used to create the initial value, which will be filled - with 'value'. + Will throw column does not exist or is not writable. - Throws if the name specified in 'colSchema' duplicates an existing obs column. + Example: - Returns a new AnnoMatrix. + dropObsColumn("old annotations") -> AnnoMatrix + */ + abstract dropObsColumn(col: LabelType): AnnoMatrix; - Examples: + /* + Add a new writable OBS annotation column, with the caller-specified schema, initial value + type and value. - addObsColumn( - { name: "foo", type: "categorical", categories: "unassigned" }, - Array, - "unassigned" - ) -> AnnoMatrix + Value may be any one of: + * an array of values + * a primitive type, including null or undefined. + If an array, length must be the same as 'this.nObs', and constructor must equal 'Ctor'. + If a primitive, 'Ctor' will be used to create the initial value, which will be filled + with 'value'. - */ - _subclassResponsibility(); - } + Throws if the name specified in 'colSchema' duplicates an existing obs column. - // @ts-expect-error ts-migrate(6133) FIXME: 'oldCol' is declared but its value is never read. - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - renameObsColumn(oldCol, newCol) { - /* - Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix. + Returns a new AnnoMatrix. - Will throw column does not exist or is not writable, or if 'newCol' is not unique. + Examples: - Example: + addObsColumn( + { name: "foo", type: "categorical", categories: "unassigned" }, + Array, + "unassigned" + ) -> AnnoMatrix - renameObsColumn('cell type', 'old cell type') -> AnnoMatrix. + */ + abstract addObsColumn( + colSchema: AnnotationColumnSchema, + Ctor: new (n: number) => T, + value: T + ): AnnoMatrix; - */ - _subclassResponsibility(); - } + /* + Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix. - // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read. - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - async setObsColumnValues(col, obsLabels, value) { - /* - Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be - 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). + Will throw column does not exist or is not writable, or if 'newCol' is not unique. - NOTE: async method, as it may need to fetch. + Example: - Will throw column does not exist or is not writable. + renameObsColumn('cell type', 'old cell type') -> AnnoMatrix. - Example: - await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix + */ + abstract renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix; - */ - _subclassResponsibility(); - } + /* + Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be + to set a group of cells to have a label on a user-created categorical annotation + (eg set all selected cells to have a label). + + NOTE: async method, as it may need to fetch. + + Will throw column does not exist or is not writable. + + Example: + await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix + */ + abstract setObsColumnValues( + col: LabelType, + obsLabels: Int32Array, + value: DataframeValue + ): Promise; - // @ts-expect-error ts-migrate(6133) FIXME: 'col' is declared but its value is never read. - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - async resetObsColumnValues(col, oldValue, newValue) { - /* - Set by value - all elements in the column with value 'oldValue' are set to 'newValue'. - Async method - returns a promise for a new AnnoMatrix. + /* + Set by value - all elements in the column with value 'oldValue' are set to 'newValue'. + Async method - returns a promise for a new AnnoMatrix. - Typical use would be to set all labels of one value to another. + Typical use would be to set all labels of one value to another. - Will throw column does not exist or is not writable. + Will throw column does not exist or is not writable. - Example: - await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix + Example: + await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix */ - _subclassResponsibility(); - } + abstract resetObsColumnValues( + col: LabelType, + oldValue: T, + newValue: T + ): Promise; - // @ts-expect-error ts-migrate(6133) FIXME: 'colSchema' is declared but its value is never rea... Remove this comment to see the full error message - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - addEmbedding(colSchema) { - /* - Add a new obs embedding to the AnnoMatrix, with provided schema. - Returns a new annomatrix. + /* + Add a new obs embedding to the AnnoMatrix, with provided schema. + Returns a new annomatrix. - Typical use will be to add a re-embedding that the server has calculated. + Typical use will be to add a re-embedding that the server has calculated. - Will throw if the column schema is invalid (eg, duplicate name). - */ - _subclassResponsibility(); - } + Will throw if the column schema is invalid (eg, duplicate name). + */ + abstract addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix; - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - getCacheKeys(field, query) { + getCacheKeys( + field: Field, + query: Query + ): WhereCacheColumnLabels | [undefined] { /* Return cache keys for columns associated with this query. May return [unknown] if no keys are known (ie, nothing is or was cached). @@ -474,26 +477,20 @@ Return cache keys for columns associated with this query. May return /** ** Private interfaces below. **/ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _resolveCachedQueries(field, queries) { - return ( - queries - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'query' implicitly has an 'any' type. - .map((query) => - _whereCacheGet(this._whereCache, this.schema, field, query).filter( - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'cacheKey' implicitly has an 'any' type. - (cacheKey) => - cacheKey !== undefined && this._cache[field].hasCol(cacheKey) - ) + _resolveCachedQueries(field: Field, queries: Query[]): LabelArray { + return queries + .map((query: Query) => + // @ts-expect-error --- TODO revisit: + // `filter`: This expression is not callable. + _whereCacheGet(this._whereCache, this.schema, field, query).filter( + (cacheKey?: LabelType) => + cacheKey !== undefined && this._cache[field].hasCol(cacheKey) ) - .flat() - ); + ) + .flat(); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async _fetch(field, q): Dataframe { + async _fetch(field: Field, q: Query | Query[]): Promise { if (!AnnoMatrix.fields().includes(field)) return Dataframe.empty(); const queries = Array.isArray(q) ? q : [q]; queries.forEach(_queryValidate); @@ -505,8 +502,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( - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'cacheKey' implicitly has an 'any' type. - (cacheKey) => + (cacheKey?: LabelType) => cacheKey === undefined || !this._cache[field].hasCol(cacheKey) ) ); @@ -515,17 +511,19 @@ Return cache keys for columns associated with this query. May return if (uncachedQueries.length > 0) { await Promise.all( uncachedQueries.map((query) => - // @ts-expect-error ts-migrate(7006) FIXME: Parameter '_field' implicitly has an 'any' type. - this._getPendingLoad(field, query, async (_field, _query) => { - /* fetch, then index. _doLoad is subclass interface */ - // @ts-expect-error ts-migrate(2488) FIXME: Type 'void' must have a '[Symbol.iterator]()' meth... Remove this comment to see the full error message - const [whereCacheUpdate, df] = await this._doLoad(_field, _query); - this._cache[_field] = this._cache[_field].withColsFrom(df); - this._whereCache = _whereCacheMerge( - this._whereCache, - whereCacheUpdate - ); - }) + this._getPendingLoad( + field, + query, + async (_field: Field, _query: Query): Promise => { + /* fetch, then index. _doLoad is subclass interface */ + const [whereCacheUpdate, df] = await this._doLoad(_field, _query); + this._cache[_field] = this._cache[_field].withColsFrom(df); + this._whereCache = _whereCacheMerge( + this._whereCache, + whereCacheUpdate + ); + } + ) ) ); } @@ -539,9 +537,11 @@ Return cache keys for columns associated with this query. May return return response; } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async _getPendingLoad(field, query, fetchFn) { + async _getPendingLoad( + field: Field, + query: Query, + fetchFn: (_field: Field, _query: Query) => Promise + ): Promise { /* 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,9 +562,22 @@ Return cache keys for columns associated with this query. May return return this._pendingLoad[field][key]; } - // eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types -- make sure subclass implements - async _doLoad() { - _subclassResponsibility(); + 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; } /** @@ -596,23 +609,21 @@ 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. */ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _gcField(field, isHot, pinnedColumns) { + _gcField(field: Field, isHot: boolean, pinnedColumns: LabelArray): void { const maxColumns = isHot ? 256 : 10; const cache = this._cache[field]; if (cache.colIndex.size() < maxColumns) return; // trivial rejection const candidates = cache.colIndex .labels() - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - .filter((col) => !pinnedColumns.includes(col)); + // @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)); const excessCount = candidates.length + pinnedColumns.length - maxColumns; if (excessCount > 0) { const { _gcInfo } = this; - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'a' implicitly has an 'any' type. - candidates.sort((a, b) => { + candidates.sort((a: LabelType, b: LabelType) => { let atime = _gcInfo.get(_columnCacheKey(field, a)); if (atime === undefined) atime = 0; @@ -629,49 +640,49 @@ 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( - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'df' implicitly has an 'any' type. - (df, col) => df.dropCol(col), + (df: Dataframe, col: LabelType) => df.dropCol(col), this._cache[field] ); - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col))); + toDrop.forEach((col: LabelType) => + _gcInfo.delete(_columnCacheKey(field, col)) + ); } } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _gcFetchCleanup(field, pinnedColumns) { + _gcFetchCleanup(field: Field, pinnedColumns: LabelArray): void { /* Called during data load/fetch. By definition, this is 'hot', so we only want to gc X. */ - if (field === "X") { + if (field === 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)) ); } } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'hints' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _gc(hints) { + _gc(hints: GCHints): void { /* 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 ? ["X"] : ["X", "emb", "var", "obs"]; + const candidateFields = isHot + ? [Field.X] + : [Field.X, Field.emb, Field.var, Field.obs]; candidateFields.forEach((field) => this._gcField(field, isHot, _getWritableColumns(this.schema, field)) ); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _gcUpdateStats(field, dataframe) { + _gcUpdateStats(field: Field, dataframe: Dataframe): void { /* 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 @@ -680,8 +691,7 @@ Return cache keys for columns associated with this query. May return const cols = dataframe.colIndex.labels(); const { _gcInfo } = this; const now = Date.now(); - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'c' implicitly has an 'any' type. - cols.forEach((c) => { + cols.forEach((c: LabelType) => { _gcInfo.set(_columnCacheKey(field, c), now); }); } @@ -698,9 +708,7 @@ Return cache keys for columns associated with this query. May return Do not override _clone(); **/ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'clone' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _cloneDeeper(clone) { + _cloneDeeper(clone: AnnoMatrix): AnnoMatrix { clone._cache = _shallowClone(this._cache); clone._gcInfo = new Map(); clone._pendingLoad = { @@ -712,8 +720,7 @@ Return cache keys for columns associated with this query. May return return clone; } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _clone() { + _clone(): AnnoMatrix { const clone = _shallowClone(this); this._cloneDeeper(clone); Object.seal(clone); @@ -724,12 +731,6 @@ Return cache keys for columns associated with this query. May return /* private utility functions below */ -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. -function _columnCacheKey(field, column) { +function _columnCacheKey(field: Field, column: LabelType): string { return `${field}/${column}`; } - -function _subclassResponsibility() { - /* protect against bugs in subclass */ - throw new Error("subclass failed to implement required method"); -} diff --git a/client/src/annoMatrix/clone.ts b/client/src/annoMatrix/clone.ts index dabe3d8e17..11f689ce63 100644 --- a/client/src/annoMatrix/clone.ts +++ b/client/src/annoMatrix/clone.ts @@ -1,7 +1,7 @@ /* Shallow clone an object, correctly handling prototype */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export default function _shallowClone(orig: any) { + +export default function _shallowClone(orig: T): T { return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig); } diff --git a/client/src/annoMatrix/crossfilter.ts b/client/src/annoMatrix/crossfilter.ts index f45dfa8e0f..d99481048f 100644 --- a/client/src/annoMatrix/crossfilter.ts +++ b/client/src/annoMatrix/crossfilter.ts @@ -9,38 +9,58 @@ AnnoMatrix stay in sync: */ import Crossfilter from "../util/typedCrossfilter"; import { _getColumnSchema } from "./schema"; - -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. -function _dimensionNameFromDf(field, df) { +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 { const colNames = df.colIndex.labels(); return _dimensionName(field, colNames); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. -function _dimensionName(field, colNames) { +function _dimensionName( + field: Field, + colNames: LabelType | LabelArray +): string { if (!Array.isArray(colNames)) return `${field}/${colNames}`; return `${field}/${colNames.join(":")}`; } export default class AnnoMatrixObsCrossfilter { - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - constructor(annoMatrix, _obsCrossfilter = null) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).annoMatrix = annoMatrix; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter = + annoMatrix: AnnoMatrix; + + obsCrossfilter: Crossfilter; + + constructor( + annoMatrix: AnnoMatrix, + _obsCrossfilter: Crossfilter | null = null + ) { + this.annoMatrix = annoMatrix; + this.obsCrossfilter = _obsCrossfilter || new Crossfilter(annoMatrix._cache.obs); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter = (this as any).obsCrossfilter.setData( - annoMatrix._cache.obs - ); + this.obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - size() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (this as any).obsCrossfilter.size(); + size(): number { + return this.obsCrossfilter.size(); } /** @@ -50,44 +70,33 @@ export default class AnnoMatrixObsCrossfilter { See API documentation in annoMatrix.js. **/ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'colSchema' implicitly has an 'any' type... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - addObsColumn(colSchema, Ctor, value) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = (this as any).annoMatrix.addObsColumn( - colSchema, - Ctor, - value - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const obsCrossfilter = (this as any).obsCrossfilter.setData( - annoMatrix._cache.obs - ); + addObsColumn( + colSchema: AnnotationColumnSchema, + Ctor: new (n: number) => T, + value: T + ): AnnoMatrixObsCrossfilter { + const annoMatrix = this.annoMatrix.addObsColumn(colSchema, Ctor, value); + const obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - dropObsColumn(col) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = (this as any).annoMatrix.dropObsColumn(col); - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message + dropObsColumn(col: LabelType): AnnoMatrixObsCrossfilter { + const annoMatrix = this.annoMatrix.dropObsColumn(col); let { obsCrossfilter } = this; - const dimName = _dimensionName("obs", col); + const dimName = _dimensionName(Field.obs, col); if (obsCrossfilter.hasDimension(dimName)) { obsCrossfilter = obsCrossfilter.delDimension(dimName); } return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'oldCol' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - renameObsColumn(oldCol, newCol) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = (this as any).annoMatrix.renameObsColumn(oldCol, newCol); - const oldDimName = _dimensionName("obs", oldCol); - const newDimName = _dimensionName("obs", newCol); - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message + renameObsColumn( + oldCol: LabelType, + newCol: LabelType + ): AnnoMatrixObsCrossfilter { + const annoMatrix = this.annoMatrix.renameObsColumn(oldCol, newCol); + const oldDimName = _dimensionName(Field.obs, oldCol); + const newDimName = _dimensionName(Field.obs, newCol); let { obsCrossfilter } = this; if (obsCrossfilter.hasDimension(oldDimName)) { obsCrossfilter = obsCrossfilter.renameDimension(oldDimName, newDimName); @@ -95,16 +104,12 @@ export default class AnnoMatrixObsCrossfilter { return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - addObsAnnoCategory(col, category) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = (this as any).annoMatrix.addObsAnnoCategory( - col, - category - ); - const dimName = _dimensionName("obs", col); - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message + addObsAnnoCategory( + col: LabelType, + category: string + ): AnnoMatrixObsCrossfilter { + const annoMatrix = this.annoMatrix.addObsAnnoCategory(col, category); + const dimName = _dimensionName(Field.obs, col); let { obsCrossfilter } = this; if (obsCrossfilter.hasDimension(dimName)) { obsCrossfilter = obsCrossfilter.delDimension(dimName); @@ -112,17 +117,17 @@ export default class AnnoMatrixObsCrossfilter { return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async removeObsAnnoCategory(col, category, unassignedCategory) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = await (this as any).annoMatrix.removeObsAnnoCategory( + async removeObsAnnoCategory( + col: LabelType, + category: string, + unassignedCategory: string + ): Promise { + const annoMatrix = await this.annoMatrix.removeObsAnnoCategory( col, category, unassignedCategory ); - const dimName = _dimensionName("obs", col); - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message + const dimName = _dimensionName(Field.obs, col); let { obsCrossfilter } = this; if (obsCrossfilter.hasDimension(dimName)) { obsCrossfilter = obsCrossfilter.delDimension(dimName); @@ -130,17 +135,17 @@ export default class AnnoMatrixObsCrossfilter { return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async setObsColumnValues(col, rowLabels, value) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = await (this as any).annoMatrix.setObsColumnValues( + async setObsColumnValues( + col: LabelType, + rowLabels: Int32Array, + value: DataframeValue + ): Promise { + const annoMatrix = await this.annoMatrix.setObsColumnValues( col, rowLabels, value ); - const dimName = _dimensionName("obs", col); - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message + const dimName = _dimensionName(Field.obs, col); let { obsCrossfilter } = this; if (obsCrossfilter.hasDimension(dimName)) { obsCrossfilter = obsCrossfilter.delDimension(dimName); @@ -148,17 +153,17 @@ export default class AnnoMatrixObsCrossfilter { return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async resetObsColumnValues(col, oldValue, newValue) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = await (this as any).annoMatrix.resetObsColumnValues( + async resetObsColumnValues( + col: LabelType, + oldValue: T, + newValue: T + ): Promise { + const annoMatrix = await this.annoMatrix.resetObsColumnValues( col, oldValue, newValue ); - const dimName = _dimensionName("obs", col); - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message + const dimName = _dimensionName(Field.obs, col); let { obsCrossfilter } = this; if (obsCrossfilter.hasDimension(dimName)) { obsCrossfilter = obsCrossfilter.delDimension(dimName); @@ -166,35 +171,25 @@ export default class AnnoMatrixObsCrossfilter { return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'colSchema' implicitly has an 'any' type... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - addEmbedding(colSchema) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const annoMatrix = (this as any).annoMatrix.addEmbedding(colSchema); - return new AnnoMatrixObsCrossfilter( - annoMatrix, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter - ); + addEmbedding(colSchema: EmbeddingSchema): AnnoMatrixObsCrossfilter { + 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 trackin the selection state, but aren't sure we want to blow the + * want to stop tracking the selection state, but aren't sure we want to blow the * annomatrix cache. */ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - dropDimension(field, query) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Anno... Remove this comment to see the full error message + dropDimension(field: Field, query: Query): AnnoMatrixObsCrossfilter { const { annoMatrix } = this; - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message let { obsCrossfilter } = this; const keys = annoMatrix .getCacheKeys(field, query) - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'k' implicitly has an 'any' type. - .filter((k) => k !== undefined); - const dimName = _dimensionName(field, keys); + // @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[]); if (obsCrossfilter.hasDimension(dimName)) { obsCrossfilter = obsCrossfilter.delDimension(dimName); } @@ -206,12 +201,13 @@ export default class AnnoMatrixObsCrossfilter { are just wrappers to lazy create indices. **/ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async select(field, query, spec) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Anno... Remove this comment to see the full error message + 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 { const { annoMatrix } = this; - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsCrossfilter' does not exist on type '... Remove this comment to see the full error message let { obsCrossfilter } = this; if (!annoMatrix?._cache?.[field]) { @@ -223,7 +219,9 @@ 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 @@ -240,86 +238,66 @@ export default class AnnoMatrixObsCrossfilter { return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - selectAll() { + selectAll(): AnnoMatrixObsCrossfilter { /* Select all on any dimension in this field. */ - // @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Anno... Remove this comment to see the full error message const { annoMatrix } = this; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const currentDims = (this as any).obsCrossfilter.dimensionNames(); + const currentDims = this.obsCrossfilter.dimensionNames(); const obsCrossfilter = currentDims.reduce( - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'xfltr' implicitly has an 'any' type. (xfltr, dim) => xfltr.select(dim, { mode: "all" }), - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter - ); // eslint-disable-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. + this.obsCrossfilter + ); return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - countSelected() { + countSelected(): number { /* if no data yet indexed in the crossfilter, just say everything is selected */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - if ((this as any).obsCrossfilter.size() === 0) - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (this as any).annoMatrix.nObs; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (this as any).obsCrossfilter.countSelected(); + if (this.obsCrossfilter.size() === 0) return this.annoMatrix.nObs; + return this.obsCrossfilter.countSelected(); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - allSelectedMask() { + allSelectedMask(): Uint8Array { /* if no data yet indexed in the crossfilter, just say everything is selected */ if ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter.size() === 0 || - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter.dimensionNames().length === 0 + this.obsCrossfilter.size() === 0 || + this.obsCrossfilter.dimensionNames().length === 0 ) { /* fake the mask */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return new Uint8Array((this as any).annoMatrix.nObs).fill(1); + return new Uint8Array(this.annoMatrix.nObs).fill(1); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (this as any).obsCrossfilter.allSelectedMask(); + return this.obsCrossfilter.allSelectedMask(); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - allSelectedLabels() { + allSelectedLabels(): LabelArray { /* if no data yet indexed in the crossfilter, just say everything is selected */ if ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter.size() === 0 || - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter.dimensionNames().length === 0 + this.obsCrossfilter.size() === 0 || + this.obsCrossfilter.dimensionNames().length === 0 ) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (this as any).annoMatrix.rowIndex.labels(); + return this.annoMatrix.rowIndex.labels(); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const mask = (this as any).obsCrossfilter.allSelectedMask(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const index = (this as any).annoMatrix.rowIndex.isubsetMask(mask); + const mask = this.obsCrossfilter.allSelectedMask(); + const index = this.annoMatrix.rowIndex.isubsetMask(mask); return index.labels(); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'array' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - fillByIsSelected(array, selectedValue, deselectedValue) { + fillByIsSelected( + array: A, + selectedValue: A[0], + deselectedValue: A[0] + ): A { /* if no data yet indexed in the crossfilter, just say everything is selected */ if ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter.size() === 0 || - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).obsCrossfilter.dimensionNames().length === 0 + 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); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (this as any).obsCrossfilter.fillByIsSelected( + return this.obsCrossfilter.fillByIsSelected( array, selectedValue, deselectedValue @@ -330,37 +308,35 @@ export default class AnnoMatrixObsCrossfilter { ** Private below **/ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _addObsCrossfilterDimension(annoMatrix, obsCrossfilter, field, df) { + _addObsCrossfilterDimension( + annoMatrix: AnnoMatrix, + obsCrossfilter: Crossfilter, + field: Field, + df: Dataframe + ): Crossfilter { 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(2488) FIXME: Type 'any[] | undefined' must have a '[Symbol.iter... Remove this comment to see the full error message + // @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; } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _getColumnBaseType(field, col) { + _getColumnBaseType(field: Field, col: LabelType): string { /* Look up the primitive type for this field/col */ - const colSchema = _getColumnSchema( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).annoMatrix.schema, - field, - col - ); + const colSchema = _getColumnSchema(this.annoMatrix.schema, field, col); return colSchema.type; } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - _getObsDimensionParams(field, df) { + _getObsDimensionParams( + field: Field, + df: Dataframe + ): ObsDimensionParams | undefined { /* return the crossfilter dimensiontype type and params for this field/dataframe */ - if (field === "emb") { + if (field === Field.emb) { /* assumed to be 2D */ return ["spatial", df.icol(0).asArray(), df.icol(1).asArray()]; } @@ -368,6 +344,8 @@ 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()]; diff --git a/client/src/annoMatrix/fetchHelpers.ts b/client/src/annoMatrix/fetchHelpers.ts index 994979f90b..27d2198f02 100644 --- a/client/src/annoMatrix/fetchHelpers.ts +++ b/client/src/annoMatrix/fetchHelpers.ts @@ -1,8 +1,7 @@ export { doBinaryRequest, doFetch } from "../util/actionHelpers"; /* double URI encode - needed for query-param filters */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _dubEncURIComp(s: any) { +export function _dubEncURIComp(s: string | number | boolean): string { return encodeURIComponent(encodeURIComponent(s)); } diff --git a/client/src/annoMatrix/loader.ts b/client/src/annoMatrix/loader.ts index 1d996bf2bd..1480132fc6 100644 --- a/client/src/annoMatrix/loader.ts +++ b/client/src/annoMatrix/loader.ts @@ -2,33 +2,46 @@ import { doBinaryRequest, doFetch } from "./fetchHelpers"; import { matrixFBSToDataframe } from "../util/stateManager/matrix"; import { _getColumnSchema } from "./schema"; import { - addObsAnnoColumn, - removeObsAnnoColumn, addObsAnnoCategory, - removeObsAnnoCategory, + addObsAnnoColumn, addObsLayout, + removeObsAnnoCategory, + removeObsAnnoColumn, } from "../util/stateManager/schemaHelpers"; import { isAnyArray } from "../common/types/arraytypes"; -import { _whereCacheCreate } from "./whereCache"; +import { _whereCacheCreate, WhereCache } from "./whereCache"; import AnnoMatrix from "./annoMatrix"; import PromiseLimit from "../util/promiseLimit"; import { - _expectSimpleQuery, _expectComplexQuery, - _urlEncodeLabelQuery, - _urlEncodeComplexQuery, + _expectSimpleQuery, _hashStringValues, + _urlEncodeComplexQuery, + _urlEncodeLabelQuery, + ComplexQuery, + Query, } 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 { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - baseURL: any; + baseURL: string; /* AnnoMatrix implementation which proxies to HTTP server using the CXG REST API. @@ -40,8 +53,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix { new AnnoMatrixLoader(serverBaseURL, schema) -> instance */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - constructor(baseURL: any, schema: any) { + constructor(baseURL: string, schema: RawSchema) { const { nObs, nVar } = schema.dataframe; super(schema, nObs, nVar); @@ -56,33 +68,36 @@ export default class AnnoMatrixLoader extends AnnoMatrix { /** ** Public. API described in base class. **/ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - addObsAnnoCategory(col: any, category: any) { + addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix { /* Add a new category (aka label) to the schema for an obs column. */ - const colSchema = _getColumnSchema(this.schema, "obs", col); - _writableCategoryTypeCheck(colSchema); // throws on error + const colSchema = _getColumnSchema( + this.schema, + Field.obs, + col + ) as AnnotationColumnSchema; + _writableObsCategoryTypeCheck(colSchema); // throws on error const newAnnoMatrix = this._clone(); newAnnoMatrix.schema = addObsAnnoCategory(this.schema, col, category); return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. async removeObsAnnoCategory( - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - col: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - category: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - unassignedCategory: any - ) { + col: LabelType, + category: string, + unassignedCategory: string + ): Promise { /* Remove a single "category" (aka "label") from the data & schema of an obs column. */ - const colSchema = _getColumnSchema(this.schema, "obs", col); - _writableCategoryTypeCheck(colSchema); // throws on error + const colSchema = _getColumnSchema( + this.schema, + Field.obs, + col + ) as AnnotationColumnSchema; + _writableObsCategoryTypeCheck(colSchema); // throws on error const newAnnoMatrix = await this.resetObsColumnValues( col, @@ -97,24 +112,28 @@ export default class AnnoMatrixLoader extends AnnoMatrix { return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - dropObsColumn(col: any) { + dropObsColumn(col: LabelType): AnnoMatrix { /* drop column from field */ - const colSchema = _getColumnSchema(this.schema, "obs", col); - _writableCheck(colSchema); // throws on error + const colSchema = _getColumnSchema( + this.schema, + Field.obs, + col + ) as AnnotationColumnSchema; + _writableObsCheck(colSchema); // throws on error const newAnnoMatrix = this._clone(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - newAnnoMatrix._cache.obs = (this as any)._cache.obs.dropCol(col); + newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col); newAnnoMatrix.schema = removeObsAnnoColumn(this.schema, col); return newAnnoMatrix; } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'colSchema' implicitly has an 'any' type... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - addObsColumn(colSchema, Ctor, value) { + addObsColumn( + colSchema: AnnotationColumnSchema, + Ctor: new (n: number) => T, + value: T + ): AnnoMatrix { /* add a column to field, initializing with value. Value may be one of: @@ -125,9 +144,8 @@ export default class AnnoMatrixLoader extends AnnoMatrix { colSchema.writable = true; const colName = colSchema.name; if ( - _getColumnSchema(this.schema, "obs", colName) || - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any)._cache.obs.hasCol(colName) + _getColumnSchema(this.schema, Field.obs, colName) || + this._cache.obs.hasCol(colName) ) { throw new Error("column already exists"); } @@ -143,8 +161,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix { } else { data = new Ctor(this.nObs).fill(value); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - newAnnoMatrix._cache.obs = (this as any)._cache.obs.withCol(colName, data); + newAnnoMatrix._cache.obs = this._cache.obs.withCol(colName, data); normalizeWritableCategoricalSchema( colSchema, newAnnoMatrix._cache.obs.col(colName) @@ -153,47 +170,55 @@ export default class AnnoMatrixLoader extends AnnoMatrix { return newAnnoMatrix; } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'oldCol' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - renameObsColumn(oldCol, newCol) { + renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix { /* Rename the obs oldColName to newColName. oldCol must be writable. */ - const oldColSchema = _getColumnSchema(this.schema, "obs", oldCol); - _writableCheck(oldColSchema); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const value = (this as any)._cache.obs.hasCol(oldCol) - ? // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any)._cache.obs.col(oldCol).asArray() + const oldColSchema = _getColumnSchema( + this.schema, + Field.obs, + oldCol + ) as AnnotationColumnSchema; + _writableObsCheck(oldColSchema); + 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 ); } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async setObsColumnValues(col, rowLabels, value) { + async setObsColumnValues( + col: LabelType, + rowLabels: Int32Array, + value: DataframeValue + ): Promise { /* Set all rows identified by rowLabels to value. */ - const colSchema = _getColumnSchema(this.schema, "obs", col); - _writableCategoryTypeCheck(colSchema); // throws on error + const colSchema = _getColumnSchema( + this.schema, + Field.obs, + col + ) as AnnotationColumnSchema; + _writableObsCategoryTypeCheck(colSchema); // throws on error // ensure that we have the data in cache before we manipulate it - await this.fetch("obs", col); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - if (!(this as any)._cache.obs.hasCol(col)) + await this.fetch(Field.obs, col); + if (!this._cache.obs.hasCol(col)) throw new Error("Internal error - user annotation data missing"); const rowIndices = this.rowIndex.getOffsets(rowLabels); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const data = (this as any)._cache.obs.col(col).asArray().slice(); + const data = this._cache.obs.col(col).asArray().slice(); for (let i = 0, len = rowIndices.length; i < len; i += 1) { const idx = rowIndices[i]; if (idx === -1) throw new Error("Unknown row label"); @@ -201,11 +226,7 @@ export default class AnnoMatrixLoader extends AnnoMatrix { } const newAnnoMatrix = this._clone(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - newAnnoMatrix._cache.obs = (this as any)._cache.obs.replaceColData( - col, - data - ); + newAnnoMatrix._cache.obs = this._cache.obs.replaceColData(col, data); const { categories } = colSchema; if (!categories?.includes(value)) { newAnnoMatrix.schema = addObsAnnoCategory(this.schema, col, value); @@ -213,37 +234,39 @@ export default class AnnoMatrixLoader extends AnnoMatrix { return newAnnoMatrix; } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'col' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async resetObsColumnValues(col, oldValue, newValue) { + async resetObsColumnValues( + col: LabelType, + oldValue: T, + newValue: T + ): Promise { /* Set all rows with value 'oldValue' to 'newValue'. */ - const colSchema = _getColumnSchema(this.schema, "obs", col); - _writableCategoryTypeCheck(colSchema); // throws on error - + const colSchema = _getColumnSchema( + this.schema, + Field.obs, + col + ) as AnnotationColumnSchema; + _writableObsCategoryTypeCheck(colSchema); // throws on error + + // @ts-expect-error ts-migrate --- TODO revisit: + // `colSchema.categories`: Object is possibly 'undefined'. if (!colSchema.categories.includes(oldValue)) { throw new Error("unknown category"); } // ensure that we have the data in cache before we manipulate it - await this.fetch("obs", col); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - if (!(this as any)._cache.obs.hasCol(col)) + await this.fetch(Field.obs, col); + if (!this._cache.obs.hasCol(col)) throw new Error("Internal error - user annotation data missing"); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - const data = (this as any)._cache.obs.col(col).asArray().slice(); + const data = this._cache.obs.col(col).asArray().slice(); for (let i = 0, l = data.length; i < l; i += 1) { if (data[i] === oldValue) data[i] = newValue; } const newAnnoMatrix = this._clone(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - newAnnoMatrix._cache.obs = (this as any)._cache.obs.replaceColData( - col, - data - ); + newAnnoMatrix._cache.obs = this._cache.obs.replaceColData(col, data); const { categories } = colSchema; if (!categories?.includes(newValue)) { newAnnoMatrix.schema = addObsAnnoCategory(this.schema, col, newValue); @@ -251,14 +274,12 @@ export default class AnnoMatrixLoader extends AnnoMatrix { return newAnnoMatrix; } - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'colSchema' implicitly has an 'any' type... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - addEmbedding(colSchema) { + addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix { /* add new layout to the obs embeddings */ const { name: colName } = colSchema; - if (_getColumnSchema(this.schema, "emb", colName)) { + if (_getColumnSchema(this.schema, Field.emb, colName)) { throw new Error("column already exists"); } @@ -270,9 +291,10 @@ export default class AnnoMatrixLoader extends AnnoMatrix { /** ** Private below **/ - // @ts-expect-error ts-migrate(2416) FIXME: Property '_doLoad' in type 'AnnoMatrixLoader' is n... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async _doLoad(field, query) { + async _doLoad( + field: Field, + query: Query + ): Promise<[WhereCache | null, Dataframe]> { /* _doLoad - evaluates the query against the field. Returns: * whereCache update: column query map mapping the query to the column labels @@ -300,7 +322,8 @@ export default class AnnoMatrixLoader extends AnnoMatrix { throw new Error("Unknown field name"); } const buffer = await promiseThrottle.priorityAdd(priority, doRequest); - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'unknown' is not assignable to parameter of type 'ArrayBuffer | ArrayBuffer[]'.... Remove this comment to see the full error message + // @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"); @@ -320,23 +343,26 @@ export default class AnnoMatrixLoader extends AnnoMatrix { Utility functions below */ -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'colSchema' implicitly has an 'any' type... Remove this comment to see the full error message -function _writableCheck(colSchema) { - if (!colSchema?.writable) { +function _writableObsCheck(obsColSchema: AnnotationColumnSchema): void { + if (!obsColSchema?.writable) { throw new Error("Unknown or readonly obs column"); } } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'colSchema' implicitly has an 'any' type... Remove this comment to see the full error message -function _writableCategoryTypeCheck(colSchema) { - _writableCheck(colSchema); - if (colSchema.type !== "categorical") { +function _writableObsCategoryTypeCheck( + obsColSchema: AnnotationColumnSchema +): void { + _writableObsCheck(obsColSchema); + if (obsColSchema.type !== "categorical") { throw new Error("column must be categorical"); } } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'baseURL' implicitly has an 'any' type. -function _embLoader(baseURL, _field, query) { +function _embLoader( + baseURL: string, + _field: Field, + query: Query +): () => Promise { _expectSimpleQuery(query); const urlBase = `${baseURL}layout/obs`; @@ -345,8 +371,11 @@ function _embLoader(baseURL, _field, query) { return () => doBinaryRequest(url); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'baseURL' implicitly has an 'any' type. -function _obsOrVarLoader(baseURL, field, query) { +function _obsOrVarLoader( + baseURL: string, + field: Field, + query: Query +): () => Promise { _expectSimpleQuery(query); const urlBase = `${baseURL}annotations/${field}`; @@ -355,20 +384,26 @@ function _obsOrVarLoader(baseURL, field, query) { return () => doBinaryRequest(url); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'baseURL' implicitly has an 'any' type. -function _XLoader(baseURL, field, query) { +function _XLoader( + baseURL: string, + _field: Field, + query: Query +): () => Promise { _expectComplexQuery(query); - if (query.where) { + // Casting here as query is validated to be complex in _expectComplexQuery above. + const complexQuery = query as ComplexQuery; + + if ("where" in complexQuery) { const urlBase = `${baseURL}data/var`; - const urlQuery = _urlEncodeComplexQuery(query); + const urlQuery = _urlEncodeComplexQuery(complexQuery); const url = `${urlBase}?${urlQuery}`; return () => doBinaryRequest(url); } - if (query.summarize) { + if ("summarize" in complexQuery) { const urlBase = `${baseURL}summarize/var`; - const urlQuery = _urlEncodeComplexQuery(query); + const urlQuery = _urlEncodeComplexQuery(complexQuery); if (urlBase.length + urlQuery.length < 2000) { const url = `${urlBase}?${urlQuery}`; diff --git a/client/src/annoMatrix/middleware.ts b/client/src/annoMatrix/middleware.ts index 581dc24b3d..07f9c0b43c 100644 --- a/client/src/annoMatrix/middleware.ts +++ b/client/src/annoMatrix/middleware.ts @@ -11,17 +11,24 @@ Undoable metareducer and the AnnoMatrix private API. It would be helpful to make the Undoable interface better factored. */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -const annoMatrixGC = (store: any) => (next: any) => (action: any) => { - if (_itIsTimeForGC()) { - _doGC(store); - } - return next(action); -}; +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 => { + if (_itIsTimeForGC()) { + _doGC(store); + } + return next(action); + }; let lastGCTime = 0; const InterGCDelayMS = 30 * 1000; // 30 seconds -function _itIsTimeForGC() { +function _itIsTimeForGC(): boolean { /* we don't want to run GC on every dispatch, so throttle it a bit. @@ -35,20 +42,22 @@ function _itIsTimeForGC() { return false; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -function _doGC(store: any) { +function _doGC(store: MiddlewareAPI): void { const state = store.getState(); // these should probably be a function imported from undoable.js, etc, as - // they have overly intimiate knowledge of our reducers. + // they have overly intimate 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 --- FIXME: disabled temporarily on migrate to TS. + // eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers .flatMap((snapshot: any) => - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - snapshot.filter((v: any) => v[0] === "annoMatrix").map((v: any) => v[1]) + snapshot + // eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers + .filter((v: any) => v[0] === "annoMatrix") + // eslint-disable-next-line @typescript-eslint/no-explicit-any --- TODO revisit: waiting for typings from /reducers + .map((v: any) => v[1]) ); const currentAnnoMatrix = state.annoMatrix; @@ -56,18 +65,16 @@ function _doGC(store: any) { 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( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - undoableStack.map((m: any) => [m, { isHot: false }]) + const allAnnoMatrices = new Map( + undoableStack.map((m: AnnoMatrix) => [m, { isHot: false }]) ); let am = currentAnnoMatrix; - while (am) { + while (am?.isView) { allAnnoMatrices.set(am, { isHot: true }); am = am.viewOf; } - allAnnoMatrices.forEach((hints, annoMatrix) => - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (annoMatrix as any)._gc(hints) + allAnnoMatrices.forEach((hints, annoMatrix: AnnoMatrix) => + annoMatrix._gc(hints) ); } diff --git a/client/src/annoMatrix/normalize.ts b/client/src/annoMatrix/normalize.ts index 1296cc801d..a00183831a 100644 --- a/client/src/annoMatrix/normalize.ts +++ b/client/src/annoMatrix/normalize.ts @@ -1,4 +1,3 @@ -import { Schema } from "../common/types/schema"; import { _getColumnSchema, _isIndex } from "./schema"; import catLabelSort from "../util/catLabelSort"; import { @@ -7,9 +6,15 @@ import { globalConfig, } from "../globals"; import { Dataframe, LabelType, DataframeColumn } from "../util/dataframe"; +import { + AnnotationColumnSchema, + ArraySchema, + Field, + Schema, +} from "../common/types/schema"; export function normalizeResponse( - field: string, + field: Field, schema: Schema, response: Dataframe ): Dataframe { @@ -37,11 +42,15 @@ export function normalizeResponse( */ // currently no data or schema normalization necessary for X or emb - if (field !== "obs" && field !== "var") return response; + if (field !== Field.obs && field !== Field.var) return response; const colLabels = response.colIndex.labels(); for (const colLabel of colLabels) { - const colSchema = _getColumnSchema(schema, field, colLabel); + const colSchema = _getColumnSchema( + schema, + field, + colLabel + ) as AnnotationColumnSchema; const isIndex = _isIndex(schema, field, colLabel); const { type, writable } = colSchema; @@ -73,11 +82,10 @@ function castColumnToBoolean(df: Dataframe, label: LabelType): Dataframe { return df; } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. export function normalizeWritableCategoricalSchema( - colSchema: any, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. + colSchema: AnnotationColumnSchema, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. col: DataframeColumn -) { +): ArraySchema { /* Ensure all enum writable / categorical schema have a categories array, that the categories array contains all unique values in the data array, AND that @@ -96,7 +104,7 @@ export function normalizeWritableCategoricalSchema( export function normalizeCategorical( df: Dataframe, colLabel: LabelType, - colSchema: any // eslint-disable-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. + colSchema: AnnotationColumnSchema ): Dataframe { /* If writable, ensure schema matches data and we have an unassigned label @@ -106,7 +114,6 @@ export function normalizeCategorical( */ const { writable } = colSchema; const col = df.col(colLabel); - if (writable) { // writable (aka user) annotations normalizeWritableCategoricalSchema(colSchema, col); diff --git a/client/src/annoMatrix/query.ts b/client/src/annoMatrix/query.ts index a01a58c01a..f2a1b0ed97 100644 --- a/client/src/annoMatrix/query.ts +++ b/client/src/annoMatrix/query.ts @@ -1,22 +1,52 @@ 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 {object | string} query - the query - * @returns {object | string} - the normalized query + * @param {Query} query - the query + * @returns {Query} - the normalized query */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _queryValidate(query: any) { +export function _queryValidate(query: Query): Query { if (typeof query !== "object") return query; - if (query.where && query.summarize) + if ("where" in query && "summarize" in query) throw new Error("query may not specify both where and summarize"); - if (query.where) { + if ("where" in query) { const { field: queryField, column: queryColumn, @@ -26,7 +56,7 @@ export function _queryValidate(query: any) { throw new Error("Incomplete where query"); return query; } - if (query.summarize) { + if ("summarize" in query) { const { field: queryField, column: queryColumn, @@ -41,13 +71,7 @@ export function _queryValidate(query: any) { throw new Error("query must specify one of where or summarize"); } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _expectSimpleQuery(query: any) { - if (typeof query === "object") throw new Error("expected simple query"); -} - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _expectComplexQuery(query: any) { +export function _expectComplexQuery(query: Query): void { if (typeof query !== "object") throw new Error("expected complex query"); } @@ -56,13 +80,12 @@ export function _expectComplexQuery(query: any) { * * @param {string} field * @param {string|object} query - * @returns the key + * @returns {string} the key */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _queryCacheKey(field: any, query: any) { +export function _queryCacheKey(field: Field, query: Query): string { if (typeof query === "object") { // complex query - if (query.where) { + if ("where" in query) { const { field: queryField, column: queryColumn, @@ -70,7 +93,7 @@ export function _queryCacheKey(field: any, query: any) { } = query.where; return `${field}/${queryField}/${queryColumn}/${queryValue}`; } - if (query.summarize) { + if ("summarize" in query) { const { method, field: queryField, @@ -88,38 +111,34 @@ export function _queryCacheKey(field: any, query: any) { return `${field}/${query}`; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -function _urlEncodeWhereQuery(q: any) { +function _urlEncodeWhereQuery(q: WhereQueryTerm): string { const { field: queryField, column: queryColumn, value: queryValue } = q; return `${_dubEncURIComp(queryField)}:${_dubEncURIComp( queryColumn )}=${_dubEncURIComp(queryValue)}`; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -function _urlEncodeSummarizeQuery(q: any) { +function _urlEncodeSummarizeQuery(q: SummarizeQueryTerm): string { const { method, field, column, values } = q; - const filter = values // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - .map((value: any) => _urlEncodeWhereQuery({ field, column, value })) + const filter = values + .map((value: string) => _urlEncodeWhereQuery({ field, column, value })) .join("&"); return `method=${method}&${filter}`; } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _urlEncodeComplexQuery(q: any) { +export function _urlEncodeComplexQuery(q: ComplexQuery): string { if (typeof q === "object") { - if (q.where) { + if ("where" in q) { return _urlEncodeWhereQuery(q.where); } - if (q.summarize) { + if ("summarize" in q) { return _urlEncodeSummarizeQuery(q.summarize); } } throw new Error("Unrecognized complex query type"); } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _urlEncodeLabelQuery(colKey: any, q: any) { +export function _urlEncodeLabelQuery(colKey: string, q: Query): string { 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)}`; @@ -128,8 +147,6 @@ export function _urlEncodeLabelQuery(colKey: any, q: any) { /** * Generate the column key the server will send us for this query. */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _hashStringValues(arrayOfString: any) { - const hash = sha1(arrayOfString.join("")); - return hash; +export function _hashStringValues(arrayOfString: string[]): string { + return sha1(arrayOfString.join("")); } diff --git a/client/src/annoMatrix/schema.ts b/client/src/annoMatrix/schema.ts index 28e88ab58b..0224655e0a 100644 --- a/client/src/annoMatrix/schema.ts +++ b/client/src/annoMatrix/schema.ts @@ -1,37 +1,54 @@ /* Private helper functions related to schema */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _getColumnSchema(schema: any, field: any, col: any) { +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 { /* look up the column definition */ switch (field) { - case "obs": + case Field.obs: if (typeof col === "object") throw new Error("unable to get column schema by query"); return schema.annotations.obsByName[col]; - case "var": + case Field.var: if (typeof col === "object") throw new Error("unable to get column schema by query"); return schema.annotations.varByName[col]; - case "emb": + case Field.emb: if (typeof col === "object") throw new Error("unable to get column schema by query"); return schema.layout.obsByName[col]; - case "X": + case Field.X: return schema.dataframe; default: throw new Error(`unknown field name: ${field}`); } } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function _isIndex(schema: any, field: any, col: any): boolean { +export function _isIndex( + schema: Schema, + field: Field.obs | Field.var, + col: LabelType +): boolean { const index = schema.annotations?.[field].index; - return index && index === col; + return !!(index && index === col); } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -export function _getColumnDimensionNames(schema: any, field: any, col: any) { +export function _getColumnDimensionNames( + schema: Schema, + field: Field, + col: LabelType +): LabelArray | undefined { /* 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 @@ -41,40 +58,33 @@ export function _getColumnDimensionNames(schema: any, field: any, col: any) { if (!colSchema) { return undefined; } - return colSchema.dims || [col]; + if ("dims" in colSchema) { + return colSchema.dims; + } + return [col]; } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'schema' implicitly has an 'any' type. -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function _schemaColumns(schema, field) { +export function _schemaColumns(schema: Schema, field: Field): string[] { switch (field) { - case "obs": + case Field.obs: return Object.keys(schema.annotations.obsByName); - case "var": + case Field.var: return Object.keys(schema.annotations.varByName); - case "emb": + case Field.emb: return Object.keys(schema.layout.obsByName); default: throw new Error(`unknown field name: ${field}`); } } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'schema' implicitly has an 'any' type. -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function _getWritableColumns(schema, field) { - if (field !== "obs") return []; - return ( - schema.annotations.obs.columns - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'v' implicitly has an 'any' type. - .filter((v) => v.writable) - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'v' implicitly has an 'any' type. - .map((v) => v.name) - ); +export function _getWritableColumns(schema: Schema, field: Field): string[] { + if (field !== Field.obs) return []; + return schema.annotations.obs.columns + .filter((v: AnnotationColumnSchema) => v.writable) + .map((v: AnnotationColumnSchema) => v.name); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'schema' implicitly has an 'any' type. -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function _isContinuousType(schema) { +export function _isContinuousType(schema: ArraySchema): boolean { const { type } = schema; return !(type === "string" || type === "boolean" || type === "categorical"); } diff --git a/client/src/annoMatrix/viewCreators.ts b/client/src/annoMatrix/viewCreators.ts index bc5a6eab45..4cd4d02d80 100644 --- a/client/src/annoMatrix/viewCreators.ts +++ b/client/src/annoMatrix/viewCreators.ts @@ -4,9 +4,18 @@ 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"; -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function isubsetMask(annoMatrix: any, obsMask: any) { +export function isubsetMask( + annoMatrix: AnnoMatrix, + obsMask: Uint8Array +): AnnoMatrixRowSubsetView { /* Subset annomatrix to contain the rows which have truish value in the mask. Maks length must equal annoMatrix.nObs (row count). @@ -14,9 +23,10 @@ export function isubsetMask(annoMatrix: any, obsMask: any) { return isubset(annoMatrix, _maskToList(obsMask)); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function isubset(annoMatrix, obsOffsets) { +export function isubset( + annoMatrix: AnnoMatrix, + obsOffsets: OffsetArray +): AnnoMatrixRowSubsetView { /* Subset annomatrix to contain the positions contained in the obsOffsets array @@ -28,9 +38,10 @@ export function isubset(annoMatrix, obsOffsets) { return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function subset(annoMatrix, obsLabels) { +export function subset( + annoMatrix: AnnoMatrix, + obsLabels: Int32Array +): AnnoMatrixRowSubsetView { /* subset based on labels */ @@ -38,18 +49,21 @@ export function subset(annoMatrix, obsLabels) { return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function subsetByIndex(annoMatrix, obsIndex) { +export function subsetByIndex( + annoMatrix: AnnoMatrix, + obsIndex: DenseInt32Index | IdentityInt32Index | KeyIndex +): AnnoMatrixRowSubsetView { /* subset based upon the new obs index. */ return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function clip(annoMatrix, qmin, qmax) { +export function clip( + annoMatrix: AnnoMatrix, + qmin: number, + qmax: number +): AnnoMatrix { /* 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 @@ -62,12 +76,8 @@ export function clip(annoMatrix, qmin, qmax) { Private utility functions below */ -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'mask' implicitly has an 'any' type. -function _maskToList(mask) { +function _maskToList(mask: Uint8Array): OffsetArray { /* 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) { diff --git a/client/src/annoMatrix/views.ts b/client/src/annoMatrix/views.ts index 1614ef9087..7ddfb6db28 100644 --- a/client/src/annoMatrix/views.ts +++ b/client/src/annoMatrix/views.ts @@ -5,21 +5,40 @@ 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 } from "./whereCache"; +import { _whereCacheCreate, WhereCache } from "./whereCache"; import { _isContinuousType, _getColumnSchema } from "./schema"; - -class AnnoMatrixView extends AnnoMatrix { - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - constructor(viewOf: any, rowIndex = null) { - // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. +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) { const nObs = rowIndex ? rowIndex.size() : viewOf.nObs; super(viewOf.schema, nObs, viewOf.nVar, rowIndex || viewOf.rowIndex); this.viewOf = viewOf; this.isView = true; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - addObsAnnoCategory(col: any, category: any) { + addObsAnnoCategory(col: LabelType, category: string): AnnoMatrix { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = this.viewOf.addObsAnnoCategory(col, category); newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; @@ -27,13 +46,10 @@ class AnnoMatrixView extends AnnoMatrix { } async removeObsAnnoCategory( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - col: any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - category: any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - unassignedCategory: any - ) { + col: LabelType, + category: string, + unassignedCategory: string + ): Promise { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = await this.viewOf.removeObsAnnoCategory( col, @@ -44,8 +60,7 @@ class AnnoMatrixView extends AnnoMatrix { return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - dropObsColumn(col: any) { + dropObsColumn(col: LabelType): AnnoMatrix { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = this.viewOf.dropObsColumn(col); newAnnoMatrix._cache.obs = this._cache.obs.dropCol(col); @@ -53,24 +68,29 @@ class AnnoMatrixView extends AnnoMatrix { return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - addObsColumn(colSchema: any, Ctor: any, value: any) { + addObsColumn( + colSchema: AnnotationColumnSchema, + Ctor: new (n: number) => T, + value: T + ): AnnoMatrix { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = this.viewOf.addObsColumn(colSchema, Ctor, value); newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - renameObsColumn(oldCol: any, newCol: any) { + renameObsColumn(oldCol: LabelType, newCol: LabelType): AnnoMatrix { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = this.viewOf.renameObsColumn(oldCol, newCol); newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - async setObsColumnValues(col: any, rowLabels: any, value: any) { + async setObsColumnValues( + col: LabelType, + rowLabels: Int32Array, + value: DataframeValue + ): Promise { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = await this.viewOf.setObsColumnValues( col, @@ -82,8 +102,11 @@ class AnnoMatrixView extends AnnoMatrix { return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - async resetObsColumnValues(col: any, oldValue: any, newValue: any) { + async resetObsColumnValues( + col: LabelType, + oldValue: T, + newValue: T + ): Promise { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = await this.viewOf.resetObsColumnValues( col, @@ -95,8 +118,7 @@ class AnnoMatrixView extends AnnoMatrix { return newAnnoMatrix; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - addEmbedding(colSchema: any) { + addEmbedding(colSchema: EmbeddingSchema): AnnoMatrix { const newAnnoMatrix = this._clone(); newAnnoMatrix.viewOf = this.viewOf.addEmbedding(colSchema); newAnnoMatrix.schema = newAnnoMatrix.viewOf.schema; @@ -105,26 +127,32 @@ class AnnoMatrixView extends AnnoMatrix { } class AnnoMatrixMapView extends AnnoMatrixView { + mapFn: MapFn; + /* - A view which knows how to transform its data. - */ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'viewOf' implicitly has an 'any' type. - constructor(viewOf, mapFn) { + A view which knows how to transform its data. + */ + constructor(viewOf: AnnoMatrix, mapFn: MapFn) { super(viewOf); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).mapFn = mapFn; + this.mapFn = mapFn; } - // @ts-expect-error ts-migrate(2416) FIXME: Property '_doLoad' in type 'AnnoMatrixMapView' is ... Remove this comment to see the full error message - async _doLoad(field, query) { + async _doLoad( + field: Field, + query: Query + ): Promise<[WhereCache | null, Dataframe]> { const df = await this.viewOf._fetch(field, query); - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'colData' implicitly has an 'any' type. - const dfMapped = df.mapColumns((colData, colIdx) => { - const colLabel = df.colIndex.getLabel(colIdx); - const colSchema = _getColumnSchema(this.schema, field, colLabel); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (this as any).mapFn(field, colLabel, colSchema, colData, df); - }); + const dfMapped = df.mapColumns( + (colData: DataframeValueArray, colIdx: number) => { + 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, @@ -135,42 +163,47 @@ class AnnoMatrixMapView extends AnnoMatrixView { } export class AnnoMatrixClipView extends AnnoMatrixMapView { + clipRange: [number, number]; + + isClipped: boolean; + /* - A view which is a clipped transformation of its parent - */ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'viewOf' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - constructor(viewOf, qmin, qmax) { - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. - super(viewOf, (field, colLabel, colSchema, colData, df) => - _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax) + 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) ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).isClipped = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (this as any).clipRange = [qmin, qmax]; + this.isClipped = true; + this.clipRange = [qmin, qmax]; Object.seal(this); } } export class AnnoMatrixRowSubsetView extends AnnoMatrixView { /* - A view which is a subset of total rows. - */ - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'viewOf' implicitly has an 'any' type. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - constructor(viewOf, rowIndex) { + A view which is a subset of total rows. + */ + constructor(viewOf: AnnoMatrix, rowIndex: LabelIndexBase) { super(viewOf, rowIndex); Object.seal(this); } - // @ts-expect-error ts-migrate(2416) FIXME: Property '_doLoad' in type 'AnnoMatrixRowSubsetVie... Remove this comment to see the full error message - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - async _doLoad(field, query) { + async _doLoad( + field: Field, + query: Query + ): Promise<[WhereCache | null, Dataframe]> { const df = await this.viewOf._fetch(field, query); // don't try to row-subset the var dimension. - if (field === "var") { + if (field === Field.var) { return [null, df]; } @@ -188,10 +221,17 @@ export class AnnoMatrixRowSubsetView extends AnnoMatrixView { Utility functions below */ -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. -function _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax) { +function _clipAnnoMatrix( + field: Field, + colLabel: LabelType, + colSchema: ArraySchema, + colData: DataframeValueArray, + df: Dataframe, + qmin: number, + qmax: number +): DataframeValueArray { /* only clip obs and var scalar columns */ - if (field !== "obs" && field !== "X") return colData; + if (field !== Field.obs && field !== Field.X) return colData; if (!_isContinuousType(colSchema)) return colData; if (qmin < 0) qmin = 0; if (qmax > 1) qmax = 1; diff --git a/client/src/annoMatrix/whereCache.ts b/client/src/annoMatrix/whereCache.ts index 79fb0d9518..440565c41e 100644 --- a/client/src/annoMatrix/whereCache.ts +++ b/client/src/annoMatrix/whereCache.ts @@ -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 acutal data cache, +resolved by that query. These labels are then used to manage the actual data cache, which stores data by the resolved label. There are three query forms: @@ -49,19 +49,33 @@ creates a cache entry of: } */ import { _getColumnDimensionNames } from "./schema"; -import { _hashStringValues } from "./query"; +import { _hashStringValues, Query } from "./query"; +import { Field, Schema } from "../common/types/schema"; +import { LabelArray } from "../util/dataframe/types"; + +export interface WhereCache { + summarize?: { + [key: string]: { + [key: string]: WhereCacheTerms; + }; + }; + where?: { + [key: string]: WhereCacheTerms; + }; +} + +export type WhereCacheColumnLabels = LabelArray; + +interface WhereCacheTerms { + [key: string]: Map>; +} -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. export function _whereCacheGet( - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - whereCache: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - schema: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - field: 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 -) { + whereCache: WhereCache, + schema: Schema, + field: Field, + query: Query +): WhereCacheColumnLabels | [undefined] { /* query will either be an where query (object) or a column name (string). @@ -69,7 +83,7 @@ export function _whereCacheGet( */ if (typeof query === "object") { - if (query.where) { + if ("where" in query) { const { field: queryField, column: queryColumn, @@ -78,7 +92,7 @@ export function _whereCacheGet( const columnMap = whereCache?.where?.[field]?.[queryField]; return columnMap?.get(queryColumn)?.get(queryValue) ?? [undefined]; } - if (query.summarize) { + if ("summarize" in query) { const { method, field: queryField, @@ -95,15 +109,17 @@ export function _whereCacheGet( return _getColumnDimensionNames(schema, field, query) ?? [undefined]; } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'field' implicitly has an 'any' type. -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function _whereCacheCreate(field, query, columnLabels) { +export function _whereCacheCreate( + field: Field, + query: Query, + columnLabels: LabelArray +): WhereCache | null { /* Create a new whereCache */ if (typeof query !== "object") return null; - if (query.where) { + if ("where" in query) { const { field: queryField, column: queryColumn, @@ -119,7 +135,7 @@ export function _whereCacheCreate(field, query, columnLabels) { }, }; } - if (query.summarize) { + if ("summarize" in query) { const { method, field: queryField, @@ -143,23 +159,25 @@ export function _whereCacheCreate(field, query, columnLabels) { return {}; } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'dst' implicitly has an 'any' type. -function __mergeQueries(dst, src) { +function __mergeQueries(dst: WhereCacheTerms, src: WhereCacheTerms) { for (const [queryField, columnMap] of Object.entries(src)) { dst[queryField] = dst[queryField] || new Map(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - for (const [queryColumn, valueMap] of columnMap as any) { + 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); } } } } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'dst' implicitly has an 'any' type. -function __whereCacheMerge(dst, src) { +function __whereCacheMerge( + dst: WhereCache, + src: WhereCache | null +): WhereCache { /* merge src into dst (modifies dst) */ @@ -176,7 +194,6 @@ function __whereCacheMerge(dst, src) { dst.summarize = dst.summarize || {}; for (const [field, method] of Object.entries(src.summarize)) { dst.summarize[field] = dst.summarize[field] || {}; - // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. for (const [methodName, query] of Object.entries(method)) { dst.summarize[field][methodName] = dst.summarize[field][methodName] || {}; @@ -187,8 +204,6 @@ function __whereCacheMerge(dst, src) { return dst; } -// @ts-expect-error ts-migrate(7019) FIXME: Rest parameter 'caches' implicitly has an 'any[]' ... Remove this comment to see the full error message -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function _whereCacheMerge(...caches) { - return caches.reduce(__whereCacheMerge, {}); +export function _whereCacheMerge(...caches: (WhereCache | null)[]): WhereCache { + return caches.reduce(__whereCacheMerge, {} as WhereCache); } diff --git a/client/src/common/types/entities.ts b/client/src/common/types/entities.ts index fabef2188d..49b6362cfd 100644 --- a/client/src/common/types/entities.ts +++ b/client/src/common/types/entities.ts @@ -1,3 +1,8 @@ // If a globally shared type or interface doesn't have a clear owner, put it here -export {}; +/** + * Flags informing garbage collection-related logic. + */ +export interface GCHints { + isHot: boolean; +} diff --git a/client/src/common/types/schema.ts b/client/src/common/types/schema.ts index 12dbb34f4b..3ce10637b7 100644 --- a/client/src/common/types/schema.ts +++ b/client/src/common/types/schema.ts @@ -1,4 +1,4 @@ -type Category = number | string | boolean; +export type Category = number | string | boolean; export interface AnnotationColumnSchema { categories?: Category[]; @@ -7,7 +7,7 @@ export interface AnnotationColumnSchema { writable: boolean; } -interface XMatrixSchema { +export interface XMatrixSchema { nObs: number; nVar: number; // TODO(thuang): Not sure what other types are available @@ -56,3 +56,21 @@ 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", +} diff --git a/client/src/util/stateManager/schemaHelpers.ts b/client/src/util/stateManager/schemaHelpers.ts index 84056754e6..68c97f307b 100644 --- a/client/src/util/stateManager/schemaHelpers.ts +++ b/client/src/util/stateManager/schemaHelpers.ts @@ -14,6 +14,7 @@ import { EmbeddingSchema, AnnotationColumnSchema, } from "../../common/types/schema"; +import { LabelType } from "../dataframe/types"; /* System wide schema assumptions: @@ -75,7 +76,7 @@ function _reindexObsLayout(schema: Schema) { return schema; } -export function removeObsAnnoColumn(schema: Schema, name: string): Schema { +export function removeObsAnnoColumn(schema: Schema, name: LabelType): Schema { const newSchema = _copyObsAnno(schema); newSchema.annotations.obs.columns = schema.annotations.obs.columns.filter( (v) => v.name !== name @@ -97,7 +98,7 @@ export function addObsAnnoColumn( export function removeObsAnnoCategory( schema: Schema, - name: string, + name: LabelType, category: string ): Schema { /* remove a category from a categorical annotation */