diff --git a/client/__tests__/util/annoMatrix/serverMocks/schema.ts b/client/__tests__/util/annoMatrix/serverMocks/schema.ts index fe995b05..f957a352 100644 --- a/client/__tests__/util/annoMatrix/serverMocks/schema.ts +++ b/client/__tests__/util/annoMatrix/serverMocks/schema.ts @@ -1,4 +1,6 @@ -export const schema = { +import { RawSchema } from "../../../../src/common/types/entities"; + +export const schema: { schema: RawSchema } = { schema: { annotations: { obs: { diff --git a/client/__tests__/util/stateManager/sampleResponses.ts b/client/__tests__/util/stateManager/sampleResponses.ts index 2efd786e..77c15689 100644 --- a/client/__tests__/util/stateManager/sampleResponses.ts +++ b/client/__tests__/util/stateManager/sampleResponses.ts @@ -5,6 +5,7 @@ import zip from "lodash.zip"; import _ from "lodash"; import { flatbuffers } from "flatbuffers"; import { NetEncoding } from "../../../src/util/stateManager/matrix_generated"; +import { RawSchema } from "../../../src/common/types/entities"; /* test data mocking REST 0.2 API responses. Used in several tests. @@ -29,7 +30,7 @@ const aConfigResponse = { }, }; -const aSchemaResponse = { +const aSchemaResponse: { schema: RawSchema } = { schema: { dataframe: { nObs, @@ -40,28 +41,30 @@ const aSchemaResponse = { obs: { index: "name", columns: [ - { name: "name", type: "string" }, - { name: "field1", type: "int32" }, - { name: "field2", type: "float32" }, - { name: "field3", type: "boolean" }, + { name: "name", type: "string", writable: false }, + { name: "field1", type: "int32", writable: false }, + { name: "field2", type: "float32", writable: false }, + { name: "field3", type: "boolean", writable: false }, { name: "field4", type: "categorical", categories: field4Categories, + writable: false, }, ], }, var: { index: "name", columns: [ - { name: "name", type: "string" }, - { name: "fieldA", type: "int32" }, - { name: "fieldB", type: "float32" }, - { name: "fieldC", type: "boolean" }, + { name: "name", type: "string", writable: false }, + { name: "fieldA", type: "int32", writable: false }, + { name: "fieldB", type: "float32", writable: false }, + { name: "fieldC", type: "boolean", writable: false }, { name: "fieldD", type: "categorical", categories: fieldDCategories, + writable: false, }, ], }, diff --git a/client/package.json b/client/package.json index 073e5a97..4b08ebf4 100644 --- a/client/package.json +++ b/client/package.json @@ -12,9 +12,10 @@ "e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.ts", "e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.ts", "fmt": "eslint --fix src __tests__", - "lint": "eslint --fix src __tests__", + "lint": "eslint src __tests__ & npm run type-check", "prod": "npm run build -- configuration/webpack/webpack.config.prod.js", - "test": "jest --testPathIgnorePatterns e2e" + "test": "jest --testPathIgnorePatterns e2e", + "type-check": "tsc --noEmit" }, "engineStrict": true, "engines": { diff --git a/client/src/common/types/entities.ts b/client/src/common/types/entities.ts index bdeea455..c1f2e8e4 100644 --- a/client/src/common/types/entities.ts +++ b/client/src/common/types/entities.ts @@ -1 +1,60 @@ // If a globally shared type or interface doesn't have a clear owner, put it here + +export type Category = number | string | boolean; + +export interface AnnotationColumn { + categories?: Category[]; + name: string; + type: "string" | "float32" | "int32" | "categorical" | "boolean"; + writable: boolean; +} + +interface DataFrame { + nObs: number; + nVar: number; + // TODO(thuang): Not sure what other types are available + type: "float32"; +} + +export interface LayoutColumn { + dims: string[]; + name: string; + // TODO(thuang): Not sure what other types are available + type: "float32"; +} +interface RawLayout { + obs: LayoutColumn[]; + var?: LayoutColumn[]; +} + +interface RawAnnotations { + obs: { + columns: AnnotationColumn[]; + index: string; + }; + var: { + columns: AnnotationColumn[]; + index: string; + }; +} + +export interface RawSchema { + annotations: RawAnnotations; + dataframe: DataFrame; + layout: RawLayout; +} + +interface Annotations extends RawAnnotations { + obsByName: { [name: string]: AnnotationColumn }; + varByName: { [name: string]: AnnotationColumn }; +} + +interface Layout extends RawLayout { + obsByName: { [name: string]: LayoutColumn }; + varByName: { [name: string]: LayoutColumn }; +} + +export interface Schema extends RawSchema { + annotations: Annotations; + layout: Layout; +} diff --git a/client/src/util/fromEntries.ts b/client/src/util/fromEntries.ts index 22ae60b9..c961597f 100644 --- a/client/src/util/fromEntries.ts +++ b/client/src/util/fromEntries.ts @@ -1,15 +1,17 @@ -// 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 fromEntries(arr: any) { +export default function fromEntries( + arr: [string | number, T][] +): { [key: string]: T } { /* Similar to Object.fromEntries, but only handles array. - This could be replaced with the standard fucnction once it + This could be replaced with the standard function once it is widely available. As of 3/20/2019, it has not yet been released in the Chrome stable channel. */ - const obj = {}; - for (let i = 0, l = arr.length; i < l; i += 1) { - // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message + const obj: { [key: string]: T } = {}; + + for (let i = 0; i < arr.length; i += 1) { obj[arr[i][0]] = arr[i][1]; } + return obj; } diff --git a/client/src/util/stateManager/annotationsHelpers.ts b/client/src/util/stateManager/annotationsHelpers.ts index e8ad554e..0ff6e9d0 100644 --- a/client/src/util/stateManager/annotationsHelpers.ts +++ b/client/src/util/stateManager/annotationsHelpers.ts @@ -3,6 +3,8 @@ Helper functions for user-editable annotations state management. See also reducers/annotations.js */ +import { Schema } from "../../common/types/entities"; + /* There are a number of state constraints assumed throughout the application: @@ -16,34 +18,36 @@ application: In addition, the current state management only allows for categorical annotations to be writable. */ - -// @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 isCategoricalAnnotation(schema, name) { - /* +export function isCategoricalAnnotation( + schema: Schema, + name: string +): boolean | undefined { + /* we treat any string, categorical or boolean as a categorical. - Return true/false/undefined (for unkonwn fields) */ const colSchema = schema.annotations.obsByName[name]; + if (colSchema === undefined) return undefined; + const { type } = colSchema; + return type === "string" || type === "boolean" || type === "categorical"; } -// @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 isContinuousAnnotation(schema, name) { - /* - Return true/false/undefined - */ +export function isContinuousAnnotation( + schema: Schema, + name: string +): boolean | undefined { const colSchema = schema.annotations.obsByName[name]; + if (colSchema === undefined) return undefined; + const { type } = colSchema; + return !(type === "string" || type === "boolean" || type === "categorical"); } -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'schema' implicitly has an 'any' type. -function _isUserAnnotation(schema, name) { +function _isUserAnnotation(schema: Schema, name: string): boolean { return schema.annotations.obsByName[name]?.writable || false; } @@ -72,9 +76,8 @@ export function allHaveLabelByMask(df, colName, label, mask) { } const legalCharacters = /^(\w|[ .()-])+$/; -// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'name' implicitly has an 'any' type. -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -export function annotationNameIsErroneous(name) { + +export function annotationNameIsErroneous(name: string): boolean | string { /* Validate the name - return: * false - a valid name @@ -101,6 +104,6 @@ export function annotationNameIsErroneous(name) { } } - /* all is well! Indicte not erroneous with a false */ + /* all is well! Indicate not erroneous with a false */ return false; } diff --git a/client/src/util/stateManager/schemaHelpers.ts b/client/src/util/stateManager/schemaHelpers.ts index 090e18cf..eaf2aeed 100644 --- a/client/src/util/stateManager/schemaHelpers.ts +++ b/client/src/util/stateManager/schemaHelpers.ts @@ -8,6 +8,12 @@ import cloneDeep from "lodash.clonedeep"; import fromEntries from "../fromEntries"; import catLabelSort from "../catLabelSort"; +import { + RawSchema, + Schema, + LayoutColumn, + AnnotationColumn, +} from "../../common/types/entities"; /* System wide schema assumptions: @@ -15,31 +21,25 @@ System wide schema assumptions: - schema will be internally self-consistent (eg, index matches columns) */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function indexEntireSchema(schema: any) { +export function indexEntireSchema(schema: RawSchema): Schema { /* Index schema for ease of use */ - schema.annotations.obsByName = fromEntries( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - schema.annotations?.obs?.columns?.map((v: any) => [v.name, v]) ?? [] + (schema as Schema).annotations.obsByName = fromEntries( + schema.annotations?.obs?.columns?.map((v) => [v.name, v]) || [] ); - schema.annotations.varByName = fromEntries( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - schema.annotations?.var?.columns?.map((v: any) => [v.name, v]) ?? [] + (schema as Schema).annotations.varByName = fromEntries( + schema.annotations?.var?.columns?.map((v) => [v.name, v]) || [] ); - schema.layout.obsByName = fromEntries( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - schema.layout?.obs?.map((v: any) => [v.name, v]) ?? [] + (schema as Schema).layout.obsByName = fromEntries( + schema.layout?.obs?.map((v) => [v.name, v]) || [] ); - schema.layout.varByName = fromEntries( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - schema.layout?.var?.map((v: any) => [v.name, v]) ?? [] + (schema as Schema).layout.varByName = fromEntries( + schema.layout?.var?.map((v) => [v.name, v]) || [] ); - return schema; + return schema as Schema; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -function _copyObsAnno(schema: any) { +function _copyObsAnno(schema: Schema): Schema { /* redux copy conventions - WARNING, only for modifying obs annotations */ return { ...schema, @@ -50,8 +50,7 @@ function _copyObsAnno(schema: any) { }; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -function _copyObsLayout(schema: any) { +function _copyObsLayout(schema: Schema): Schema { return { ...schema, layout: { @@ -61,57 +60,62 @@ function _copyObsLayout(schema: any) { }; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -function _reindexObsAnno(schema: any) { +function _reindexObsAnno(schema: Schema): Schema { /* reindex obs annotations ONLY */ schema.annotations.obsByName = fromEntries( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - schema.annotations.obs.columns.map((v: any) => [v.name, v]) + schema.annotations.obs.columns.map((v) => [v.name, v]) ); return schema; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -function _reindexObsLayout(schema: any) { +function _reindexObsLayout(schema: Schema) { schema.layout.obsByName = fromEntries( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - schema.layout.obs.map((v: any) => [v.name, v]) + schema.layout.obs.map((v) => [v.name, v]) ); return 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 removeObsAnnoColumn(schema: any, name: any) { +export function removeObsAnnoColumn(schema: Schema, name: string): Schema { const newSchema = _copyObsAnno(schema); newSchema.annotations.obs.columns = schema.annotations.obs.columns.filter( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (v: any) => v.name !== name + (v) => v.name !== name ); return _reindexObsAnno(newSchema); } -// @ts-expect-error ts-migrate(6133) FIXME: 'name' is declared but its value is never read. -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function addObsAnnoColumn(schema: any, name: any, defn: any) { +export function addObsAnnoColumn( + schema: Schema, + _: string, + defn: AnnotationColumn +): Schema { const newSchema = _copyObsAnno(schema); + newSchema.annotations.obs.columns.push(defn); + return _reindexObsAnno(newSchema); } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function removeObsAnnoCategory(schema: any, name: any, category: any) { +export function removeObsAnnoCategory( + schema: Schema, + name: string, + category: string +): Schema { /* remove a category from a categorical annotation */ const categories = schema.annotations.obsByName[name]?.categories; - if (!categories) + + if (!categories) { throw new Error("column does not exist or is not categorical"); + } const idx = categories.indexOf(category); + if (idx === -1) throw new Error("category does not exist"); const newSchema = _reindexObsAnno(_copyObsAnno(schema)); /* remove category. Do not need to resort as this can't change presentation order */ - newSchema.annotations.obsByName[name].categories.splice(idx, 1); + newSchema.annotations.obsByName[name].categories?.splice(idx, 1); + return newSchema; } @@ -129,26 +133,27 @@ export function addObsAnnoCategory(schema: any, name: any, category: any) { /* add category, retaining presentation sort order */ const catAnno = newSchema.annotations.obsByName[name]; + catAnno.categories = catLabelSort(catAnno.writable, [ - ...catAnno.categories, + ...(catAnno.categories || []), category, ]); + return newSchema; } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function addObsLayout(schema: any, layout: any) { +export function addObsLayout(schema: Schema, layout: LayoutColumn): Schema { /* add or replace a layout */ const newSchema = _copyObsLayout(schema); newSchema.layout.obs.push(layout); return _reindexObsLayout(newSchema); } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -export function removeObsLayout(schema: any, name: any) { +export function removeObsLayout(schema: Schema, name: string): Schema { /* remove a layout */ const newSchema = _copyObsLayout(schema); - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - newSchema.layout.obs = schema.layout.obs.filter((v: any) => v.name !== name); + + newSchema.layout.obs = schema.layout.obs.filter((v) => v.name !== name); + return _reindexObsLayout(newSchema); } diff --git a/client/tsconfig.json b/client/tsconfig.json index 502c05a8..b0559eac 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -31,5 +31,5 @@ "__tests__/**/*", "jest-puppeteer.config.js" ], - "exclude": ["__tests__/e2e/__snapshots__/**/*"] + "exclude": ["node_modules", "__tests__/e2e/__snapshots__/**/*"] }