diff --git a/client/__tests__/e2e/puppeteerUtils.ts b/client/__tests__/e2e/puppeteerUtils.ts index 0d3f98588..a24febd88 100644 --- a/client/__tests__/e2e/puppeteerUtils.ts +++ b/client/__tests__/e2e/puppeteerUtils.ts @@ -154,24 +154,11 @@ export async function getElementCoordinates(testId: any) { }); } -async function nameNewAnnotation() { - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - if (await isElementPresent(getTestId("annotation-dialog"))) { - await typeInto("new-annotation-name", "ignoreE2E"); - await clickOn("submit-annotation"); - - // wait for the page to load - await waitByClass("autosave-complete"); - } -} - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. export async function goToPage(url: any) { await page.goto(url, { waitUntil: "networkidle0", }); - - await nameNewAnnotation(); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. diff --git a/client/__tests__/reducers/genesetsUI.test.ts b/client/__tests__/reducers/genesetsUI.test.ts index 0c0ca6a57..2bb4ce775 100644 --- a/client/__tests__/reducers/genesetsUI.test.ts +++ b/client/__tests__/reducers/genesetsUI.test.ts @@ -1,8 +1,8 @@ -import genesetsUIReducer from "../../src/reducers/genesetsUI"; +import genesetsUIReducer, { GeneSetsUIState } from "../../src/reducers/genesetsUI"; // Format: GeneSetsUI(state,action) -const initialState = { +const initialState: GeneSetsUIState = { createGenesetModeActive: false, isEditingGenesetName: false, isAddingGenesToGeneset: false, @@ -30,7 +30,7 @@ describe("geneset UI states", () => { }); test("geneset: disable create geneset mode", () => { expect( - genesetsUIReducer(undefined, { isEditingGenesetName: false }) + genesetsUIReducer(undefined, { type: "geneset: disable rename geneset mode", isEditingGenesetName: false }) ).toMatchObject(initialState); }); diff --git a/client/favicon.png b/client/favicon.png index 58f433448..1dc549b0a 100644 Binary files a/client/favicon.png and b/client/favicon.png differ diff --git a/client/src/actions/annotation.ts b/client/src/actions/annotation.ts index b7a542f1b..b9e72a4be 100644 --- a/client/src/actions/annotation.ts +++ b/client/src/actions/annotation.ts @@ -2,10 +2,9 @@ * Action creators for user annotation */ import difference from "lodash.difference"; -import pako from "pako"; import * as globals from "../globals"; import { AppDispatch, GetState } from "../reducers"; -import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager"; +import { AnnotationsHelpers } from "../util/stateManager"; const { isUserAnnotation } = AnnotationsHelpers; @@ -313,178 +312,3 @@ export const needToSaveObsAnnotations = ( (col: any) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col) ); }; - -/** - * Save the user-created obs annotations IF any have changed. - */ -export const saveObsAnnotationsAction = - () => - async (dispatch: AppDispatch, getState: GetState): Promise => { - if (!globals.API) throw new Error("API not set"); - const state = getState(); - const { annotations, autosave } = state; - const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations; - const { lastSavedAnnoMatrix, saveInProgress } = autosave; - - const annoMatrix = state.annoMatrix.base(); - - if (saveInProgress || annoMatrix === lastSavedAnnoMatrix) return; - if (!needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix)) { - dispatch({ - type: "writable obs annotations - save complete", - lastSavedAnnoMatrix: annoMatrix, - }); - return; - } - - /** - * Else, we really do need to save - */ - dispatch({ - type: "writable obs annotations - save started", - }); - - const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix)); - const matrix = MatrixFBS.encodeMatrixFBS(df); - const compressedMatrix = pako.deflate(matrix); - try { - const queryString = - !dataCollectionNameIsReadOnly && !!dataCollectionName - ? `?annotation-collection-name=${encodeURIComponent( - dataCollectionName - )}` - : ""; - const res = await fetch( - `${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`, - { - method: "PUT", - body: compressedMatrix, - headers: new Headers({ - "Content-Type": "application/octet-stream", - }), - credentials: "include", - } - ); - if (res.ok) { - dispatch({ - type: "writable obs annotations - save complete", - lastSavedAnnoMatrix: annoMatrix, - }); - } else { - dispatch({ - type: "writable obs annotations - save error", - message: `HTTP error ${res.status} - ${res.statusText}`, - res, - }); - } - } catch (error) { - if (error instanceof Error) - dispatch({ - type: "writable obs annotations - save error", - message: error.toString(), - error, - }); - } - }; - -export const saveGenesetsAction = - () => - async (dispatch: AppDispatch, getState: GetState): Promise => { - if (!globals.API) throw new Error("API not set"); - const state = getState(); - - // bail if gene sets not available, or in readonly mode. - const { config } = state; - const { lastTid, genesets } = state.genesets; - - const genesetsAreAvailable = - config?.parameters?.annotations_genesets ?? false; - const genesetsReadonly = - config?.parameters?.annotations_genesets_readonly ?? true; - if (!genesetsAreAvailable || genesetsReadonly) { - // our non-save was completed! - return dispatch({ - type: "autosave: genesets complete", - lastSavedGenesets: genesets, - }); - } - - dispatch({ - type: "autosave: genesets started", - }); - - /* Create the JSON OTA data structure */ - const tid = (lastTid ?? 0) + 1; - const genesetsOTA = []; - for (const [name, gs] of genesets) { - const genes = []; - for (const g of gs.genes.values()) { - genes.push({ - gene_symbol: g.geneSymbol, - gene_description: g.geneDescription, - }); - } - genesetsOTA.push({ - geneset_name: name, - geneset_description: gs.genesetDescription, - genes, - }); - } - const ota = { - tid, - genesets: genesetsOTA, - }; - - /* Save to server */ - try { - const { dataCollectionNameIsReadOnly, dataCollectionName } = - state.annotations; - const queryString = - !dataCollectionNameIsReadOnly && !!dataCollectionName - ? `?annotation-collection-name=${encodeURIComponent( - dataCollectionName - )}` - : ""; - - const res = await fetch( - `${globals.API.prefix}${globals.API.version}genesets${queryString}`, - { - method: "PUT", - headers: new Headers({ - Accept: "application/json", - "Content-Type": "application/json", - }), - body: JSON.stringify(ota), - credentials: "include", - } - ); - if (!res.ok) { - return dispatch({ - type: "autosave: genesets error", - message: `HTTP error ${res.status} - ${res.statusText}`, - res, - }); - } - return await Promise.all([ - dispatch({ - type: "autosave: genesets complete", - lastSavedGenesets: genesets, - }), - dispatch({ - type: "geneset: set tid", - tid, - }), - ]); - } catch (error) { - if (error instanceof Error) - return dispatch({ - type: "autosave: genesets error", - message: error.toString(), - error, - }); - return dispatch({ - type: "autosave: genesets error", - error, - }); - } - }; diff --git a/client/src/actions/index.ts b/client/src/actions/index.ts index 271cb9df9..ee7f0267e 100644 --- a/client/src/actions/index.ts +++ b/client/src/actions/index.ts @@ -504,8 +504,6 @@ export default { annoActions.annotationDeleteLabelFromCategory, annotationRenameLabelInCategory: annoActions.annotationRenameLabelInCategory, annotationLabelCurrentSelection: annoActions.annotationLabelCurrentSelection, - saveObsAnnotationsAction: annoActions.saveObsAnnotationsAction, - saveGenesetsAction: annoActions.saveGenesetsAction, needToSaveObsAnnotations: annoActions.needToSaveObsAnnotations, layoutChoiceAction: embActions.layoutChoiceAction, setCellSetFromSelection: selnActions.setCellSetFromSelection, diff --git a/client/src/common/types/entities.ts b/client/src/common/types/entities.ts index effaae0b6..014eaaced 100644 --- a/client/src/common/types/entities.ts +++ b/client/src/common/types/entities.ts @@ -15,8 +15,6 @@ export const STANDARD_CATEGORY_NAMES = [ "disease_ontology_term_id", "ethnicity", "ethnicity_ontology_term_id", - "self_reported_ethnicity", - "self_reported_ethnicity_ontology_term_id", "is_primary_data", "organism", "organism_ontology_term_id", @@ -24,8 +22,6 @@ export const STANDARD_CATEGORY_NAMES = [ "sex", "tissue_ontology_term_id", "tissue", - "suspension_type", - "donor_id", ]; /** diff --git a/client/src/components/app.tsx b/client/src/components/app.tsx index a3e5df47e..6c0e7568a 100644 --- a/client/src/components/app.tsx +++ b/client/src/components/app.tsx @@ -16,7 +16,6 @@ import RightSideBar from "./rightSidebar"; import Legend from "./continuousLegend"; import Graph from "./graph/graph"; import MenuBar from "./menubar"; -import Autosave from "./autosave"; import Embedding from "./embedding"; import actions from "../actions"; @@ -79,7 +78,6 @@ class App extends React.Component { - ({ - idhash: - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (state as any).config?.parameters?.["annotations-user-data-idhash"] ?? null, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - annotations: (state as any).annotations, - writableCategoriesEnabled: - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (state as any).config?.parameters?.annotations ?? false, - writableGenesetsEnabled: !( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - ((state as any).config?.parameters?.annotations_genesets_readonly ?? true) - ), -})) -// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS. -class FilenameDialog extends React.Component<{}, State> { - // eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS. - constructor(props: {}) { - super(props); - this.state = { - filenameText: "", - }; - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - dismissFilenameDialog = () => {}; - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - handleCreateFilename = () => { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message - const { dispatch } = this.props; - const { filenameText } = this.state; - dispatch({ - type: "set annotations collection name", - data: filenameText, - }); - }; - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - filenameError = () => { - const legalNames = /^\w+$/; - const { filenameText } = this.state; - let err = false; - if (filenameText === "") { - // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'. - err = "empty_string"; - } else if (!legalNames.test(filenameText)) { - /* - IMPORTANT: this test must ultimately match the test applied by the - backend, which is designed to ensure a safe file name can be created - from the data collection name. If you change this, you will also need - to change the validation code in the backend, or it will have no effect. - */ - // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'boolean'. - err = "characters"; - } - return err; - }; - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - filenameErrorMessage = () => { - const err = this.filenameError(); - let markup = null; - // @ts-expect-error ts-migrate(2367) FIXME: This condition will always return 'false' since th... Remove this comment to see the full error message - if (err === "empty_string") { - markup = ( - - Name cannot be blank - - ); - // @ts-expect-error ts-migrate(2367) FIXME: This condition will always return 'false' since th... Remove this comment to see the full error message - } else if (err === "characters") { - markup = ( - - Only alphanumeric and underscore allowed - - ); - } - return markup; - }; - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - render() { - const { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message - writableCategoriesEnabled, - // @ts-expect-error ts-migrate(2339) FIXME: Property 'writableGenesetsEnabled' does not exist ... Remove this comment to see the full error message - writableGenesetsEnabled, - // @ts-expect-error ts-migrate(2339) FIXME: Property 'annotations' does not exist on type 'Rea... Remove this comment to see the full error message - annotations, - // @ts-expect-error ts-migrate(2339) FIXME: Property 'idhash' does not exist on type 'Readonly... Remove this comment to see the full error message - idhash, - } = this.props; - const { filenameText } = this.state; - // eslint-disable-next-line no-constant-condition -- delete when removing annotations code - return (writableCategoriesEnabled || writableGenesetsEnabled) && - annotations.promptForFilename && - !annotations.dataCollectionNameIsReadOnly && - !annotations.dataCollectionName && - false ? ( // TODO: delete when removing annotations code - -
{ - e.preventDefault(); - this.handleCreateFilename(); - }} - > -
-
-

Name your user generated data directory:

- - this.setState({ filenameText: e.target.value }) - } - leftIcon="tag" - data-testid="new-annotation-name" - /> -

- {/* @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1. */} - {this.filenameErrorMessage(filenameText)} -

-
-
-

- {"Your annotations are stored in this file: "} - - {filenameText}-cell-labels-{idhash}.csv - -

-

- {"Your gene sets are stored in this file: "} - - {filenameText}-gene-sets-{idhash}.csv - -

-

- (We added a unique ID to your filename) -

-
-
-
-
- - - - -
-
-
-
- ) : null; - } -} - -export default FilenameDialog; diff --git a/client/src/components/autosave/index.tsx b/client/src/components/autosave/index.tsx deleted file mode 100644 index a647b331c..000000000 --- a/client/src/components/autosave/index.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import actions from "../../actions"; -import FilenameDialog from "./filenameDialog"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. -type State = any; - -// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message -@connect((state) => ({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - annotations: (state as any).annotations, - obsAnnotationSaveInProgress: - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (state as any).autosave?.obsAnnotationSaveInProgress ?? false, - genesetSaveInProgress: - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (state as any).autosave?.genesetSaveInProgress ?? false, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - error: (state as any).autosave?.error, - writableCategoriesEnabled: - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - (state as any).config?.parameters?.annotations ?? false, - writableGenesetsEnabled: !( - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - ((state as any).config?.parameters?.annotations_genesets_readonly ?? true) - ), - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - annoMatrix: (state as any).annoMatrix, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - genesets: (state as any).genesets, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - lastSavedAnnoMatrix: (state as any).autosave?.lastSavedAnnoMatrix, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - lastSavedGenesets: (state as any).autosave?.lastSavedGenesets, -})) -// eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS. -class Autosave extends React.Component<{}, State> { - // eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS. - constructor(props: {}) { - super(props); - this.state = { - timer: null, - }; - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - componentDidMount() { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message - const { writableCategoriesEnabled, writableGenesetsEnabled } = this.props; - let { timer } = this.state; - if (timer) clearInterval(timer); - if (writableCategoriesEnabled || writableGenesetsEnabled) { - timer = setInterval(this.tick, 2500); - } else { - timer = null; - } - this.setState({ timer }); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - componentWillUnmount() { - const { timer } = this.state; - if (timer) clearInterval(timer); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - tick = () => { - const { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'dispatch' does not exist on type 'Readon... Remove this comment to see the full error message - dispatch, - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsAnnotationSaveInProgress' does not ex... Remove this comment to see the full error message - obsAnnotationSaveInProgress, - // @ts-expect-error ts-migrate(2339) FIXME: Property 'genesetSaveInProgress' does not exist on... Remove this comment to see the full error message - genesetSaveInProgress, - } = this.props; - if (!obsAnnotationSaveInProgress && this.needToSaveObsAnnotations()) { - dispatch(actions.saveObsAnnotationsAction()); - } - if (!genesetSaveInProgress && this.needToSaveGenesets()) { - dispatch(actions.saveGenesetsAction()); - } - }; - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - needToSaveObsAnnotations = () => { - /* return true if we need to save obs cell labels, false if we don't */ - // @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message - const { annoMatrix, lastSavedAnnoMatrix } = this.props; - return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix); - }; - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - needToSaveGenesets = () => { - /* return true if we need to save gene ses, false if we do not */ - // @ts-expect-error ts-migrate(2339) FIXME: Property 'genesets' does not exist on type 'Readon... Remove this comment to see the full error message - const { genesets, lastSavedGenesets } = this.props; - return genesets.initialized && genesets.genesets !== lastSavedGenesets; - }; - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - needToSave() { - return this.needToSaveGenesets() || this.needToSaveObsAnnotations(); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - saveInProgress() { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'obsAnnotationSaveInProgress' does not ex... Remove this comment to see the full error message - const { obsAnnotationSaveInProgress, genesetSaveInProgress } = this.props; - return obsAnnotationSaveInProgress || genesetSaveInProgress; - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - statusMessage() { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'error' does not exist on type 'Readonly<... Remove this comment to see the full error message - const { error } = this.props; - if (error) { - return `Autosave error: ${error}`; - } - return this.needToSave() ? "Unsaved" : "All saved"; - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. - render() { - const { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'writableCategoriesEnabled' does not exis... Remove this comment to see the full error message - writableCategoriesEnabled, - // @ts-expect-error ts-migrate(2339) FIXME: Property 'writableGenesetsEnabled' does not exist ... Remove this comment to see the full error message - writableGenesetsEnabled, - // @ts-expect-error ts-migrate(2339) FIXME: Property 'lastSavedAnnoMatrix' does not exist on t... Remove this comment to see the full error message - lastSavedAnnoMatrix, - } = this.props; - const initialDataLoadComplete = lastSavedAnnoMatrix; - if (!writableCategoriesEnabled && !writableGenesetsEnabled) return null; - return ( -
- {this.statusMessage()} - -
- ); - } -} - -export default Autosave; diff --git a/client/src/components/continuousLegend/index.tsx b/client/src/components/continuousLegend/index.tsx index bf8c365f9..be7c5ae12 100644 --- a/client/src/components/continuousLegend/index.tsx +++ b/client/src/components/continuousLegend/index.tsx @@ -1,15 +1,22 @@ import React from "react"; -import { connect } from "react-redux"; +import { Action } from "redux"; +import { connect, shallowEqual } from "react-redux"; import * as d3 from "d3"; import { interpolateCool } from "d3-scale-chromatic"; +import Async, { AsyncProps } from "react-async"; +import AnnoMatrix from "../../annoMatrix/annoMatrix"; +import { AppDispatch, RootState } from "../../reducers"; import { createColorTable, createColorQuery, + ColorTable, + ColorRange, } from "../../util/stateManager/colorHelpers"; +import { ColorsState } from "../../reducers/colors"; +import { Genesets } from "../../reducers/genesets"; // create continuous color legend -// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => { const legendHeight = 200; const legendWidth = 80; @@ -34,8 +41,7 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => { we flip the color scale as well [1, 0] instead of [0, 1] */ .node(); - // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. - const ctx = canvas.getContext("2d"); + const ctx = canvas?.getContext("2d"); const legendScale = d3 .scaleLinear() @@ -46,8 +52,8 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => { ]); /* we flip this to make viridis colors dark if high in the color scale */ // image data hackery based on http://bl.ocks.org/mbostock/048d21cf747371b11884f75ad896e5a5 - // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. - const image = ctx.createImageData(1, legendHeight); + const image = ctx?.createImageData(1, legendHeight); + if (!image) return; d3.range(legendHeight).forEach((i) => { const c = d3.rgb(colorScale(legendScale.invert(i))); image.data[4 * i] = c.r; @@ -55,8 +61,8 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => { image.data[4 * i + 2] = c.b; image.data[4 * i + 3] = 255; }); - // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'. - ctx.putImageData(image, 0, 0); + + ctx?.putImageData(image, 0, 0); // A simpler way to do the above, but possibly slower. keep in mind the legend // width is stretched because the width attr of the canvas is 1 @@ -109,61 +115,121 @@ const continuous = (selectorId: any, colorScale: any, colorAccessor: any) => { .text(colorAccessor); }; -// @ts-expect-error ts-migrate(1238) FIXME: Unable to resolve signature of class decorator whe... Remove this comment to see the full error message -@connect((state) => ({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - annoMatrix: (state as any).annoMatrix, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - colors: (state as any).colors, - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - genesets: (state as any).genesets.genesets, -})) -class ContinuousLegend extends React.Component { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - async componentDidUpdate(prevProps: any) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'annoMatrix' does not exist on type 'Read... Remove this comment to see the full error message - const { annoMatrix, colors, genesets } = this.props; - if (!colors || !annoMatrix) return; - if (colors !== prevProps?.colors || annoMatrix !== prevProps?.annoMatrix) { - const { schema } = annoMatrix; - const { colorMode, colorAccessor, userColors } = colors; - const colorQuery = createColorQuery( - colorMode, - colorAccessor, - schema, - genesets - ); - const colorDf = colorQuery ? await annoMatrix.fetch(...colorQuery) : null; - const colorTable = createColorTable( - colorMode, - colorAccessor, - colorDf, - schema, - userColors - ); - const colorScale = colorTable.scale; - // @ts-expect-error ts-migrate(2339) FIXME: Property 'range' does not exist on type '((idx: an... Remove this comment to see the full error message - const range = colorScale?.range; - // @ts-expect-error ts-migrate(2339) FIXME: Property 'domain' does not exist on type '((idx: a... Remove this comment to see the full error message - const [domainMin, domainMax] = colorScale?.domain?.() ?? [0, 0]; - /* always remove it, if it's not continuous we don't put it back. */ - d3.select("#continuous_legend").selectAll("*").remove(); - if (colorAccessor && colorScale && range && domainMin < domainMax) { - /* fragile! continuous range is 0 to 1, not [#fa4b2c, ...], make this a flag? */ - if (range()[0][0] !== "#") { - continuous( - "#continuous_legend", - // @ts-expect-error ts-migrate(2339) FIXME: Property 'domain' does not exist on type '((idx: a... Remove this comment to see the full error message - d3.scaleSequential(interpolateCool).domain(colorScale.domain()), - colorAccessor - ); - } +interface FetchedAsyncProps { + colorAccessor: string; + colorScale: ColorTable["scale"]; + range?: ColorRange; + domainMin: number; + domainMax: number; + colorMode: Action["type"]; +} + +interface StateProps { + annoMatrix: AnnoMatrix; + colors: ColorsState; + genesets: Genesets; +} +interface DispatchProps { + handleColorSuccess: () => void; +} +const mapStateToProps = (state: RootState): StateProps => ({ + annoMatrix: state.annoMatrix, + colors: state.colors, + genesets: state.genesets.genesets, +}); +const mapDispatchToProps = (dispatch: AppDispatch): DispatchProps => ({ + handleColorSuccess: () => + dispatch({ type: "color by geneset mean expression success" }), +}); + +type Props = StateProps & DispatchProps; + +class ContinuousLegend extends React.Component { + static watchAsync(props: any, prevProps: any) { + return !shallowEqual(props.watchProps, prevProps.watchProps); + } + + cachedWatchProps: StateProps | null; + + cachedAsyncProps: FetchedAsyncProps | null; + + constructor(props: Props) { + super(props); + this.cachedWatchProps = null; + this.cachedAsyncProps = null; + } + + fetchAsyncProps = async ( + props: AsyncProps + ): Promise => { + const { annoMatrix, colors, genesets } = props.watchProps as StateProps; + + if (!colors || !annoMatrix || !colors.colorMode || !colors.colorAccessor) + return null; + const { schema } = annoMatrix; + const { colorMode, colorAccessor, userColors } = colors; + const colorQuery = createColorQuery( + colorMode, + colorAccessor, + schema, + genesets + ); + const colorDf = colorQuery ? await annoMatrix.fetch(...colorQuery) : null; + const colorTable = createColorTable( + colorMode, + colorAccessor, + colorDf, + schema, + userColors + ); + const colorScale = colorTable.scale; + const range = (colorScale?.range ?? (() => [0, 0])) as ColorRange; + const [domainMin, domainMax] = colorScale?.domain?.() ?? [0, 0]; + + const result: FetchedAsyncProps = { + colorAccessor, + colorScale, + range, + domainMin, + domainMax, + colorMode, + }; + return result; + }; + + updateContinuousLegend = (asyncProps: FetchedAsyncProps) => { + const { handleColorSuccess } = this.props; + const { + colorAccessor, + colorScale, + range, + domainMin, + domainMax, + colorMode, + } = asyncProps; + if ( + colorAccessor && + colorScale && + range && + (domainMin ?? 0) < (domainMax ?? 0) + ) { + /* fragile! continuous range is 0 to 1, not [#fa4b2c, ...], make this a flag? */ + const r = range(); + if (!(typeof r === "function" && r()[0][0] === "#")) { + continuous( + "#continuous_legend", + d3.scaleSequential(interpolateCool).domain(colorScale.domain()), + colorAccessor + ); } } - } + if (colorScale && colorMode === "color by geneset mean expression") { + handleColorSuccess(); + } + }; - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. render() { + const { annoMatrix, colors, genesets } = this.props; return (
+ > + + + {(asyncProps: FetchedAsyncProps) => { + d3.select("#continuous_legend").selectAll("*").remove(); + if ( + !shallowEqual(asyncProps, this.cachedAsyncProps) && + asyncProps + ) { + this.updateContinuousLegend(asyncProps); + } + this.cachedAsyncProps = asyncProps; + return null; + }} + + +
); } } -export default ContinuousLegend; +export default connect(mapStateToProps, mapDispatchToProps)(ContinuousLegend); diff --git a/client/src/components/geneExpression/menus/genesetMenus.tsx b/client/src/components/geneExpression/menus/genesetMenus.tsx index 99f41399d..2f55208bf 100644 --- a/client/src/components/geneExpression/menus/genesetMenus.tsx +++ b/client/src/components/geneExpression/menus/genesetMenus.tsx @@ -28,6 +28,8 @@ type State = any; genesetsUI: (state as any).genesetsUI, // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. colorAccessor: (state as any).colors.colorAccessor, + // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. + colorLoading: (state as any).controls.colorLoading, })) // eslint-disable-next-line @typescript-eslint/ban-types --- FIXME: disabled temporarily on migrate to TS. class GenesetMenus extends React.PureComponent<{}, State> { @@ -82,8 +84,18 @@ class GenesetMenus extends React.PureComponent<{}, State> { }; render(): JSX.Element { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'geneset' does not exist on type 'Readonl... Remove this comment to see the full error message - const { geneset, genesetsEditable, createText, colorAccessor } = this.props; + const { + // @ts-expect-error ts-migrate(2339) FIXME: Property 'geneset' does not exist on type 'Readonl... Remove this comment to see the full error message + geneset, + // @ts-expect-error ts-migrate(2339) FIXME: Property 'genesetsEditable' does not exist on type 'Readonl... Remove this comment to see the full error message + genesetsEditable, + // @ts-expect-error ts-migrate(2339) FIXME: Property 'createText' does not exist on type 'Readonl... Remove this comment to see the full error message + createText, + // @ts-expect-error ts-migrate(2339) FIXME: Property 'colorAccessor' does not exist on type 'Readonl... Remove this comment to see the full error message + colorAccessor, + // @ts-expect-error ts-migrate(2339) FIXME: Property 'colorLoading' does not exist on type 'Readonl... Remove this comment to see the full error message + colorLoading, + } = this.props; const isColorBy = geneset === colorAccessor; @@ -151,6 +163,7 @@ class GenesetMenus extends React.PureComponent<{}, State> { active={isColorBy} intent={isColorBy ? "primary" : "none"} style={{ marginLeft: 0 }} + loading={isColorBy && colorLoading} onClick={this.handleColorByEntireGeneset} data-testclass="colorby-entire-geneset" data-testid={`${geneset}:colorby-entire-geneset`} diff --git a/client/src/reducers/annoMatrix.ts b/client/src/reducers/annoMatrix.ts index 82e302289..cdf208e9f 100644 --- a/client/src/reducers/annoMatrix.ts +++ b/client/src/reducers/annoMatrix.ts @@ -2,12 +2,17 @@ Reducer for the annoMatrix */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -const AnnoMatrix = (state = null, action: any) => { +import { AnyAction } from "redux"; +import AnnoMatrix from "../annoMatrix/annoMatrix"; + +const AnnoMatrixReducer = ( + state: AnnoMatrix | null = null, + action: AnyAction +) => { if (action.annoMatrix) { return action.annoMatrix; } return state; }; -export default AnnoMatrix; +export default AnnoMatrixReducer; diff --git a/client/src/reducers/autosave.ts b/client/src/reducers/autosave.ts deleted file mode 100644 index 6b4611a21..000000000 --- a/client/src/reducers/autosave.ts +++ /dev/null @@ -1,93 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -const Autosave = ( - state = { - // cell labels - obsAnnotationSaveInProgress: false, - lastSavedAnnoMatrix: null, - - // gene sets - genesetSaveInProgress: false, - lastSavedGenesets: null, - - // error state - error: false, - }, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - action: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - nextSharedState: any -) => { - switch (action.type) { - case "annoMatrix: init complete": { - return { - ...state, - error: false, - obsAnnotationSaveInProgress: false, - lastSavedAnnoMatrix: action.annoMatrix, - }; - } - - case "writable obs annotations - save started": { - return { - ...state, - obsAnnotationSaveInProgress: true, - }; - } - - case "writable obs annotations - save error": { - return { - ...state, - error: action.message, - obsAnnotationSaveInProgress: false, - }; - } - - case "writable obs annotations - save complete": { - const { lastSavedAnnoMatrix } = action; - return { - ...state, - obsAnnotationSaveInProgress: false, - error: false, - lastSavedAnnoMatrix, - }; - } - - case "geneset: initial load": { - return { - ...state, - genesetSaveInProgress: false, - lastSavedGenesets: nextSharedState.genesets.genesets, - }; - } - - case "autosave: genesets started": { - return { - ...state, - genesetSaveInProgress: true, - }; - } - - case "autosave: genesets error": { - return { - ...state, - genesetSaveInProgress: false, - error: action.message, - }; - } - - case "autosave: genesets complete": { - const { lastSavedGenesets } = action; - return { - ...state, - genesetSaveInProgress: false, - error: false, - lastSavedGenesets, - }; - } - - default: - return { ...state }; - } -}; - -export default Autosave; diff --git a/client/src/reducers/cascade.ts b/client/src/reducers/cascade.ts index 046c65203..1f355d41f 100644 --- a/client/src/reducers/cascade.ts +++ b/client/src/reducers/cascade.ts @@ -1,46 +1,59 @@ -// 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 cascadeReducers(arg: any) { - /* - Combine a set of cascading reducers into a single reducer. Cascading - reducers are reducers which may rely on state computed by another reducer. - Therefore, they: - - must be composed in a particular order (currently, this is a simple - linear list of reducers, run in list order) - - must have access to partially updated "next state" so they can further - derive state. +import { AnyAction, Reducer } from "redux"; +import type { RootState } from "."; - Parameter is one of: - - a Map object - - an array of tuples, [ [key1, reducer1], [key2, reducer2], ... ] - Ie, cascadeReducers([ ["a", reduceA], ["b", reduceB] ]) +export type ReducerFunction = ( + prevStateForKey: RootState[keyof RootState], + action: AnyAction, + nextState?: RootState, + prevState?: RootState +) => RootState; - Each reducer will be called with the signature: - (prevState, action, sharedNextState, sharedPrevState) => newState +type CascadedReducers = + | [string, ReducerFunction][] + | Map; - cascadeReducers will build a composite newState object, much - like combinedReducers. Additional semantics: - - reducers guaranteed to be called in order - - each reducer will receive shared objects - */ +export default function cascadeReducers(arg: CascadedReducers): Reducer { + /** + * Combine a set of cascading reducers into a single reducer. Cascading + * reducers are reducers which may rely on state computed by another reducer. + * Therefore, they: + * - must be composed in a particular order (currently, this is a simple + * linear list of reducers, run in list order) + * - must have access to partially updated "next state" so they can further + * derive state. + * + * Parameter is one of: + * - a Map object + * - an array of tuples, [ [key1, reducer1], [key2, reducer2], ... ] + * Ie, cascadeReducers([ ["a", reduceA], ["b", reduceB] ]) + * + * Each reducer will be called with the signature: + * (prevState, action, sharedNextState, sharedPrevState) => newState + * cascadeReducers will build a composite newState object, much + * like combinedReducers. Additional semantics: + * - reducers guaranteed to be called in order + * - each reducer will receive shared objects + */ const reducers = arg instanceof Map ? arg : new Map(arg); const reducerKeys = [...reducers.keys()]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - return (prevState: any, action: any) => { - const nextState = {}; + + return (prevState: RootState, action: AnyAction) => { + const nextState: RootState = {}; let stateChange = false; for (let i = 0, l = reducerKeys.length; i < l; i += 1) { const key = reducerKeys[i]; const reducer = reducers.get(key); - const prevStateForKey = prevState ? prevState[key] : undefined; - const nextStateForKey = reducer( - prevStateForKey, - action, - nextState, - prevState - ); - // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - nextState[key] = nextStateForKey; - stateChange = stateChange || nextStateForKey !== prevStateForKey; + if (reducer) { + const prevStateForKey = prevState ? prevState[key] : undefined; + const nextStateForKey = reducer( + prevStateForKey, + action, + nextState, + prevState + ); + nextState[key] = nextStateForKey; + stateChange = stateChange || nextStateForKey !== prevStateForKey; + } } return stateChange ? nextState : prevState; }; diff --git a/client/src/reducers/categoricalSelection.ts b/client/src/reducers/categoricalSelection.ts index 4f1a18617..015446bdb 100644 --- a/client/src/reducers/categoricalSelection.ts +++ b/client/src/reducers/categoricalSelection.ts @@ -1,3 +1,5 @@ +import { AnyAction } from "redux"; +import type { RootState } from "."; import { ControlsHelpers as CH } from "../util/stateManager"; /* @@ -11,15 +13,14 @@ Label state default (if missing) is up to the component, but typically true. ... } */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. +interface CategoricalSelectionState { + [key: string]: Map; +} const CategoricalSelection = ( - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - state: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - action: any, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - nextSharedState: any -) => { + state: CategoricalSelectionState, + action: AnyAction, + nextSharedState: RootState +): CategoricalSelectionState => { switch (action.type) { case "initial data load complete": case "subset to selection": diff --git a/client/src/reducers/centroidLabels.ts b/client/src/reducers/centroidLabels.ts index 5cd4767fa..5be992b60 100644 --- a/client/src/reducers/centroidLabels.ts +++ b/client/src/reducers/centroidLabels.ts @@ -1,4 +1,5 @@ -import type { Action } from "redux"; +import type { Action, AnyAction } from "redux"; +import type { RootState } from "."; export interface CentroidLabelsState { showLabels: boolean; @@ -14,15 +15,15 @@ const initialState: CentroidLabelsState = { const centroidLabels = ( state = initialState, - action: CentroidLabelsAction, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - sharedNextState: any + action: AnyAction, + sharedNextState: RootState ): CentroidLabelsState => { const { colors: { colorAccessor }, } = sharedNextState; - const showLabels = action.showLabels ?? state.showLabels; + const showLabels = + (action as CentroidLabelsAction).showLabels ?? state.showLabels; switch (action.type) { case "color by categorical metadata": diff --git a/client/src/reducers/config.ts b/client/src/reducers/config.ts index a5a57e267..50e9fe3a5 100644 --- a/client/src/reducers/config.ts +++ b/client/src/reducers/config.ts @@ -1,12 +1,24 @@ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. -const Config = ( - state = { +import { AnyAction } from "redux"; +import type { Config } from "../globals"; + +interface ConfigState { + features: Config["features"] | null; + parameters: Config["parameters"] | null; + displayNames: Config["displayNames"] | null; + loading?: boolean; + error?: Error | null; + corpora_props?: Config["corpora_props"]; + library_versions?: Config["library_versions"]; + portalUrl?: Config["portalUrl"]; + links?: Config["links"]; +} +const ConfigReducer = ( + state: ConfigState = { displayNames: null, features: null, parameters: null, }, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - action: any + action: AnyAction ) => { switch (action.type) { case "initial data load start": @@ -32,4 +44,4 @@ const Config = ( } }; -export default Config; +export default ConfigReducer; diff --git a/client/src/reducers/continuousSelection.ts b/client/src/reducers/continuousSelection.ts index 8923c2b68..0a3d6b151 100644 --- a/client/src/reducers/continuousSelection.ts +++ b/client/src/reducers/continuousSelection.ts @@ -1,4 +1,4 @@ -import type { Action } from "redux"; +import type { Action, AnyAction } from "redux"; import { makeContinuousDimensionName } from "../util/nameCreators"; @@ -16,7 +16,7 @@ export interface ContinuousSelectionState { const ContinuousSelection = ( state: ContinuousSelectionState = {}, - action: ContinuousSelectionAction + action: AnyAction ): ContinuousSelectionState => { switch (action.type) { case "reset subset": @@ -27,20 +27,20 @@ const ContinuousSelection = ( case "continuous metadata histogram start": case "continuous metadata histogram brush": case "continuous metadata histogram end": { - const name = makeContinuousDimensionName( - action.continuousNamespace, - action.selection - ); + const { continuousNamespace, selection, range } = + action as ContinuousSelectionAction; + + const name = makeContinuousDimensionName(continuousNamespace, selection); return { ...state, - [name]: action.range, + [name]: range, }; } case "continuous metadata histogram cancel": { - const name = makeContinuousDimensionName( - action.continuousNamespace, - action.selection - ); + const { continuousNamespace, selection } = + action as ContinuousSelectionAction; + + const name = makeContinuousDimensionName(continuousNamespace, selection); const { [name]: deletedField, ...newState } = state; return newState; } diff --git a/client/src/reducers/controls.ts b/client/src/reducers/controls.ts index 1a40ffec3..539a6c3f6 100644 --- a/client/src/reducers/controls.ts +++ b/client/src/reducers/controls.ts @@ -1,12 +1,20 @@ +import { AnyAction } from "redux"; + +type Level = "top" | "bottom" | ""; + +interface StackLevels { + geneLevel: Level; + scatterplotLevel: Level; +} /* logic for minimizing and maximizing pop-ups */ const minimizeMaximizePopUps = ( - geneLevel: string, + geneLevel: Level, geneIsMinimized: boolean, geneIsOpen: boolean, - scatterplotLevel: string, + scatterplotLevel: Level, scatterplotIsMinimized: boolean, scatterplotIsOpen: boolean -) => { +): StackLevels => { if ( geneIsMinimized && geneIsOpen && @@ -37,13 +45,31 @@ const minimizeMaximizePopUps = ( }; }; -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. +interface ControlsState { + loading: boolean; + error: Error | string | null; + resettingInterface: boolean; + graphInteractionMode: "zoom" | "select"; + opacityForDeselectedCells: number; + scatterplotXXaccessor: string | false; + scatterplotYYaccessor: string | false; + geneIsOpen: boolean; + scatterplotIsMinimized: boolean; + geneIsMinimized: boolean; + scatterplotLevel: Level; + scatterplotIsOpen: boolean; + geneLevel: Level; + gene: string | null; + infoError: string | null; + graphRenderCounter: number; + colorLoading: boolean; + datasetDrawer: boolean; +} const Controls = ( - state = { + state: ControlsState = { // data loading flag loading: true, error: null, - // all of the data + selection state resettingInterface: false, graphInteractionMode: "select", @@ -59,11 +85,10 @@ const Controls = ( gene: null, infoError: null, graphRenderCounter: 0 /* integer as { /* For now, log anything looking like an error to the console. @@ -105,7 +130,18 @@ const Controls = ( error: action.error, }; } - + case "color by geneset mean expression": { + return { + ...state, + colorLoading: true, + }; + } + case "color by geneset mean expression success": { + return { + ...state, + colorLoading: false, + }; + } /******************************* User Events *******************************/ @@ -152,7 +188,6 @@ const Controls = ( ); state.geneLevel = stackLevels.geneLevel; state.scatterplotLevel = stackLevels.scatterplotLevel; - // new gene clicked in the span of loading if (state.gene !== action.gene) { return { diff --git a/client/src/reducers/datasetMetadata.ts b/client/src/reducers/datasetMetadata.ts index a9e0abbc9..22c8b104d 100644 --- a/client/src/reducers/datasetMetadata.ts +++ b/client/src/reducers/datasetMetadata.ts @@ -1,9 +1,9 @@ /* - Dataset metadata reducer, modifies Portal collections-related state. + Dataset metadata reducer, modifies Portal collections-related state. */ // Core dependencies -import { Action } from "redux"; +import { Action, AnyAction } from "redux"; // App dependencies import { DatasetMetadata as IDatasetMetadata } from "../common/types/entities"; @@ -11,14 +11,14 @@ import { DatasetMetadata as IDatasetMetadata } from "../common/types/entities"; /* Action dispatched on successful response from dataset-metadata endpoint. */ -export interface DatasetMetdataAction extends Action { +export interface DatasetMetadataAction extends Action { datasetMetadata: IDatasetMetadata; error: string; portalUrl: string; } /* - Dataset metdata state; selected dataset ID and corresponding collection information. + Dataset metadata state; selected dataset ID and corresponding collection information. */ export interface DatasetMetadataState { datasetMetadata: IDatasetMetadata | null; @@ -36,7 +36,7 @@ const DatasetMetadata = ( datasetMetadata: null, portalUrl: null, }, - action: DatasetMetdataAction + action: AnyAction ): DatasetMetadataState => { switch (action.type) { case "initial data load start": @@ -45,19 +45,25 @@ const DatasetMetadata = ( loading: true, error: null, }; - case "dataset metadata load complete": + case "dataset metadata load complete": { + const { datasetMetadata, portalUrl } = action as DatasetMetadataAction; + return { ...state, loading: false, error: null, - datasetMetadata: action.datasetMetadata, - portalUrl: action.portalUrl, + datasetMetadata, + portalUrl, }; - case "initial data load error": + } + case "initial data load error": { + const { error } = action as DatasetMetadataAction; + return { ...state, - error: action.error, + error, }; + } default: return state; } diff --git a/client/src/reducers/differential.ts b/client/src/reducers/differential.ts index 6cab67ba9..fafb37924 100644 --- a/client/src/reducers/differential.ts +++ b/client/src/reducers/differential.ts @@ -1,13 +1,21 @@ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. +import { AnyAction } from "redux"; +import { LabelArray } from "../util/dataframe"; + +interface DifferentialState { + loading: boolean | null; + error: Error | string | null; + celllist1: LabelArray | null; + celllist2: LabelArray | null; +} + const Differential = ( - state = { + state: DifferentialState = { loading: null, error: null, celllist1: null, celllist2: null, }, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - action: any + action: AnyAction ) => { switch (action.type) { case "request differential expression started": diff --git a/client/src/reducers/genesets.ts b/client/src/reducers/genesets.ts index 12032483b..dc9fcdd3c 100644 --- a/client/src/reducers/genesets.ts +++ b/client/src/reducers/genesets.ts @@ -343,21 +343,6 @@ const GeneSets = ( }; } - /** - * Used by autosave to update the server synchronization TID - */ - case "geneset: set tid": { - const { tid } = action; - if (!Number.isInteger(tid) || tid < 0) - throw new Error("TID must be a positive integer number"); - if (state.lastTid !== undefined && tid < state.lastTid) - throw new Error("TID may not be decremented."); - return { - ...state, - lastTid: tid, - }; - } - case "request differential expression success": { const { data } = action; diff --git a/client/src/reducers/genesetsUI.ts b/client/src/reducers/genesetsUI.ts index 6a11a7c10..3abfeaa11 100644 --- a/client/src/reducers/genesetsUI.ts +++ b/client/src/reducers/genesetsUI.ts @@ -1,15 +1,20 @@ +import { AnyAction } from "redux"; + +export interface GeneSetsUIState { + createGenesetModeActive: boolean; + isEditingGenesetName: string | false; + isAddingGenesToGeneset: string | false; +} /* Reducers for geneset UI-state. */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. const GeneSetsUI = ( - state = { + state: GeneSetsUIState = { createGenesetModeActive: false, isEditingGenesetName: false, isAddingGenesToGeneset: false, }, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - action: any + action: AnyAction ) => { switch (action.type) { /** diff --git a/client/src/reducers/graphSelection.ts b/client/src/reducers/graphSelection.ts index 8145aae4c..702e2fced 100644 --- a/client/src/reducers/graphSelection.ts +++ b/client/src/reducers/graphSelection.ts @@ -1,11 +1,16 @@ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS. +import { AnyAction } from "redux"; +import { CrossfilterSelector } from "../util/typedCrossfilter"; + +interface GraphSelectionState { + tool: "lasso" | "brush"; + selection: CrossfilterSelector; +} const GraphSelection = ( - state = { + state: GraphSelectionState = { tool: "lasso", // what selection tool mode (lasso, brush, ...) selection: { mode: "all" }, // current selection, which is tool specific }, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. - action: any + action: AnyAction ) => { switch (action.type) { case "set clip quantiles": diff --git a/client/src/reducers/index.ts b/client/src/reducers/index.ts index ff1ab763c..e7d6b3fc8 100644 --- a/client/src/reducers/index.ts +++ b/client/src/reducers/index.ts @@ -23,7 +23,6 @@ import controls from "./controls"; import annotations from "./annotations"; import genesets from "./genesets"; import genesetsUI from "./genesetsUI"; -import autosave from "./autosave"; import centroidLabels from "./centroidLabels"; import pointDialation from "./pointDilation"; import quickGenes from "./quickGenes"; @@ -50,7 +49,6 @@ const AppReducer = undoable( ["differential", differential], ["centroidLabels", centroidLabels], ["pointDilation", pointDialation], - ["autosave", autosave], ["datasetMetadata", datasetMetadata], ]), [ diff --git a/client/src/reducers/layoutChoice.ts b/client/src/reducers/layoutChoice.ts index faa62e161..f30404b7a 100644 --- a/client/src/reducers/layoutChoice.ts +++ b/client/src/reducers/layoutChoice.ts @@ -8,7 +8,8 @@ about commonly used names. Preferentially, pick in the following order: 4. give up, use the first available */ -import type { Action } from "redux"; +import type { Action, AnyAction } from "redux"; +import type { RootState } from "."; import { EmbeddingSchema, Schema } from "../common/types/schema"; function bestDefaultLayout(layouts: Array): string { @@ -39,9 +40,8 @@ export interface LayoutChoiceAction extends Action { const LayoutChoice = ( state: LayoutChoiceState, - action: LayoutChoiceAction, - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS. - nextSharedState: any + action: AnyAction, + nextSharedState: RootState ): LayoutChoiceState => { switch (action.type) { case "initial data load complete": { @@ -55,7 +55,7 @@ const LayoutChoice = ( case "set layout choice": { const { schema } = nextSharedState.annoMatrix; - const current = action.layoutChoice; + const current = (action as LayoutChoiceAction).layoutChoice; const currentDimNames = schema.layout.obsByName[current].dims; return { ...state, current, currentDimNames }; } diff --git a/client/src/reducers/obsCrossfilter.ts b/client/src/reducers/obsCrossfilter.ts index 7c38652d4..2c637d1b7 100644 --- a/client/src/reducers/obsCrossfilter.ts +++ b/client/src/reducers/obsCrossfilter.ts @@ -2,8 +2,13 @@ Reducer for the obsCrossfilter */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -const ObsCrossfilter = (state = null, action: any) => { +import { AnyAction } from "redux"; +import { AnnoMatrixObsCrossfilter } from "../annoMatrix"; + +const ObsCrossfilter = ( + state: AnnoMatrixObsCrossfilter | null = null, + action: AnyAction +) => { if (action.obsCrossfilter) { return action.obsCrossfilter; } diff --git a/client/src/reducers/pointDilation.ts b/client/src/reducers/pointDilation.ts index 8c0ed83f1..4df9e91fe 100644 --- a/client/src/reducers/pointDilation.ts +++ b/client/src/reducers/pointDilation.ts @@ -1,10 +1,16 @@ -const initialState = { +import { AnyAction } from "redux"; + +interface PointDilationState { + metadataField: string; + categoryField: string; +} + +const initialState: PointDilationState = { metadataField: "", categoryField: "", }; -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS. -const pointDialation = (state = initialState, action: any) => { +const pointDialation = (state = initialState, action: AnyAction) => { const { metadataField, label: categoryField } = action; switch (action.type) { diff --git a/client/src/reducers/quickGenes.ts b/client/src/reducers/quickGenes.ts index 612f3b93d..15b7743c3 100644 --- a/client/src/reducers/quickGenes.ts +++ b/client/src/reducers/quickGenes.ts @@ -1,23 +1,28 @@ import uniq from "lodash.uniq"; import filter from "lodash.filter"; +import { Action, AnyAction } from "redux"; import type { RootState } from "."; import { track } from "../analytics"; import { EVENTS } from "../analytics/events"; -interface QuickGenesActions { - type: string; +interface State { + userDefinedGenes: string[]; + userDefinedGenesLoading: boolean; +} + +interface QuickGenesActions extends Action { gene: string; selection: string; data: string; } const quickGenes = ( - state: { userDefinedGenes: string[]; userDefinedGenesLoading: boolean } = { + state: State = { userDefinedGenes: [], userDefinedGenesLoading: false, }, - action: QuickGenesActions, + action: AnyAction, nextSharedState: RootState -) => { +): State => { switch (action.type) { case "request user defined gene started": { return { @@ -33,7 +38,10 @@ const quickGenes = ( } case "request user defined gene success": { const { userDefinedGenes } = state; - const _userDefinedGenes = uniq([...userDefinedGenes, action.gene]); + const { gene } = action as QuickGenesActions; + + const _userDefinedGenes = uniq([...userDefinedGenes, gene]); + return { ...state, userDefinedGenes: _userDefinedGenes, @@ -42,10 +50,10 @@ const quickGenes = ( } case "clear user defined gene": { const { userDefinedGenes } = state; - const newUserDefinedGenes = filter( - userDefinedGenes, - (d) => d !== action.gene - ); + const { gene } = action as QuickGenesActions; + + const newUserDefinedGenes = filter(userDefinedGenes, (d) => d !== gene); + return { ...state, userDefinedGenes: newUserDefinedGenes, @@ -56,7 +64,7 @@ const quickGenes = ( case "color by expression": case "set scatterplot x": case "set scatterplot y": { - const { selection, gene } = action; + const { selection, gene } = action as QuickGenesActions; const { controls } = nextSharedState; const { scatterplotXXaccessor, scatterplotYYaccessor } = controls; diff --git a/client/src/reducers/undoableConfig.ts b/client/src/reducers/undoableConfig.ts index 4b0d59198..a42ca134b 100644 --- a/client/src/reducers/undoableConfig.ts +++ b/client/src/reducers/undoableConfig.ts @@ -34,14 +34,6 @@ const skipOnActions = new Set([ "category value mouse hover start", "category value mouse hover end", - /* autosave annotations */ - "writable obs annotations - save complete", - "writable obs annotations - save started", - "writable obs annotations - save error", - "autosave: genesets started", - "autosave: genesets error", - "autosave: genesets complete", - /* annotation component action */ "annotation: activate add new label mode", "annotation: disable add new label mode", diff --git a/client/src/util/stateManager/colorHelpers.ts b/client/src/util/stateManager/colorHelpers.ts index 70fdfa2f5..0cf875fb4 100644 --- a/client/src/util/stateManager/colorHelpers.ts +++ b/client/src/util/stateManager/colorHelpers.ts @@ -21,13 +21,6 @@ import { UserColor, } from "../../reducers/colors"; -interface Colors { - // cell label to color mapping - rgb: Float32Array; - // function, mapping label index to color scale - scale: d3.ScaleSequential | undefined; -} - /** * Given a color mode & accessor, generate an annoMatrix query that will * fulfill it @@ -86,7 +79,7 @@ export function createColorQuery( } } -function _defaultColors(nObs: number): Colors { +function _defaultColors(nObs: number): ColorTable { return { rgb: new Float32Array(3 * nObs).fill(0), scale: undefined, @@ -95,6 +88,21 @@ function _defaultColors(nObs: number): Colors { const defaultColors = memoize(_defaultColors); +export type ColorRange = + | d3.ScaleSequentialBase["range"] + | d3.ScaleQuantile["range"]; +export interface ColorTable { + rgb: Float32Array; + scale: + | { + (idx: number): d3.RGBColor; + range?(): ColorRange; + domain(): number[]; + } + | d3.ScaleQuantile + | d3.ScaleSequential + | undefined; +} /** * Create colors scale and RGB array and return as object. * @@ -111,7 +119,7 @@ function _createColorTable( colorByData: Dataframe | null, schema: Schema, userColors: ConvertedUserColors | null = null -) { +): ColorTable { if (colorMode === null || colorByData === null) { return defaultColors(schema.dataframe.nObs); } @@ -189,7 +197,7 @@ function _createUserColors( colorAccessor: LabelType, schema: Schema, userColors: ConvertedUserColors -) { +): ColorTable { const { colors, scale: scaleByLabel } = userColors[colorAccessor]; const rgb = createRgbArray(data, colors); @@ -204,7 +212,7 @@ function _createUserColors( categories?.forEach((label, idx) => categoryMap.set(idx, label)); const scale = (idx: number) => scaleByLabel(categoryMap.get(idx)); - + scale.domain = () => [0, 0]; return { rgb, scale }; } const createUserColors = memoize(_createUserColors); @@ -217,7 +225,7 @@ function _createColorsByCategoricalMetadata( data: DataframeValueArray, colorAccessor: LabelType, schema: Schema -): Colors { +): ColorTable { // TODO: #35 Use type guards to insure type instead of casting const { categories } = schema.annotations.obsByName[ colorAccessor @@ -234,7 +242,6 @@ function _createColorsByCategoricalMetadata( }, {}); const rgb = createRgbArray(data, colors); - return { rgb, scale }; } @@ -261,7 +268,7 @@ function _createColorsByContinuousMetadata( data: DataframeValueArray, min: number, max: number -) { +): ColorTable { const colorBins = 100; const scale = d3 .scaleQuantile() diff --git a/server/tests/fixtures/test_bad_config.yaml b/server/tests/fixtures/test_bad_config.yaml index f851b68c8..feea9106f 100644 --- a/server/tests/fixtures/test_bad_config.yaml +++ b/server/tests/fixtures/test_bad_config.yaml @@ -14,7 +14,7 @@ obs: tissue_ontology_term_id: UBERON:12345 assay_ontology_term_id: EFO:12345 disease_ontology_term_id: MONDO:12345 - self_reported_ethnicity_ontology_term_id: MANCESTRO:12345 + ethnicity_ontology_term_id: MANCESTRO:12345 development_stage_ontology_term_id: HsapDv:12345 sex: other uns: diff --git a/server/tests/fixtures/test_config.yaml b/server/tests/fixtures/test_config.yaml index 5a841ffec..e9f7365fa 100644 --- a/server/tests/fixtures/test_config.yaml +++ b/server/tests/fixtures/test_config.yaml @@ -14,7 +14,7 @@ obs: tissue_ontology_term_id: UBERON:12345 assay_ontology_term_id: EFO:12345 disease_ontology_term_id: MONDO:12345 - self_reported_ethnicity_ontology_term_id: HANCESTRO:12345 + ethnicity_ontology_term_id: HANCESTRO:12345 development_stage_ontology_term_id: HsapDv:12345 sex: other uns: