diff --git a/src/components/ReferenceGenomesContent.js b/src/components/ReferenceGenomesContent.js index 16110a30a..2192ab01f 100644 --- a/src/components/ReferenceGenomesContent.js +++ b/src/components/ReferenceGenomesContent.js @@ -1,5 +1,4 @@ import { useCallback, useMemo } from "react"; -import { useDispatch } from "react-redux"; import { Button, Descriptions, Dropdown, Layout, Modal, Popover, Space, Table } from "antd"; import { BarsOutlined, DeleteOutlined, ImportOutlined } from "@ant-design/icons"; @@ -13,6 +12,7 @@ import { useResourcePermissionsWrapper } from "@/hooks"; import { deleteReferenceGenomeIfPossible } from "@/modules/reference/actions"; import { useReferenceGenomes } from "@/modules/reference/hooks"; import { useService, useServices, useWorkflows } from "@/modules/services/hooks"; +import { useAppDispatch } from "@/store"; import { LAYOUT_CONTENT_STYLE } from "@/styles/layoutContent"; import SitePageHeader from "./SitePageHeader"; @@ -21,7 +21,7 @@ import { useStartIngestionFlow } from "./manager/workflowCommon"; const DEFAULT_REF_INGEST_WORKFLOW_ID = "fasta_ref"; const ReferenceGenomesContent = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { hasAttempted: hasAttemptedServiceFetch } = useServices(); const { permissions } = useResourcePermissionsWrapper(RESOURCE_EVERYTHING); diff --git a/src/components/ServiceContent.js b/src/components/ServiceContent.js index 98d11c294..de339778f 100644 --- a/src/components/ServiceContent.js +++ b/src/components/ServiceContent.js @@ -1,5 +1,4 @@ import { useEffect } from "react"; -import { useSelector } from "react-redux"; import { Col, Layout, Row, Spin, Statistic, Typography } from "antd"; @@ -10,6 +9,7 @@ import { SITE_NAME } from "@/constants"; import { EM_DASH } from "@/constants"; import { BENTO_URL } from "@/config"; import { useProjects } from "@/modules/metadata/hooks"; +import { useAppSelector } from "@/store"; const ServiceContent = () => { useEffect(() => { @@ -17,7 +17,7 @@ const ServiceContent = () => { }, []); const { items: projects, isFetching: isFetchingProjects } = useProjects(); - const isFetching = useSelector((state) => state.user.isFetchingDependentData) || isFetchingProjects; + const isFetching = useAppSelector((state) => state.user.isFetchingDependentData) || isFetchingProjects; return ( <> diff --git a/src/components/SiteHeader.js b/src/components/SiteHeader.js index 21d47c3ac..97aab6742 100644 --- a/src/components/SiteHeader.js +++ b/src/components/SiteHeader.js @@ -1,5 +1,4 @@ import { memo, useCallback, useMemo, useState } from "react"; -import { useDispatch } from "react-redux"; import { Link } from "react-router-dom"; import { viewNotifications, @@ -30,16 +29,17 @@ import { import { BENTO_CBIOPORTAL_ENABLED, BENTO_GRAFANA_URL, BENTO_MONITORING_ENABLED, CUSTOM_HEADER } from "@/config"; import { useEverythingPermissions } from "@/hooks"; -import { showNotificationDrawer } from "@/modules/notifications/actions"; -import { useNotifications } from "@/modules/notifications/hooks"; -import { matchingMenuKeys, transformMenuItem } from "@/utils/menu"; - -import OverviewSettingsControl from "./overview/OverviewSettingsControl"; import { useCanQueryAtLeastOneProjectOrDataset, useHasValidGrafanaRole, useManagerPermissions, } from "@/modules/authz/hooks"; +import { showNotificationDrawer } from "@/modules/notifications/actions"; +import { useNotifications } from "@/modules/notifications/hooks"; +import { useAppDispatch } from "@/store"; +import { matchingMenuKeys, transformMenuItem } from "@/utils/menu"; + +import OverviewSettingsControl from "./overview/OverviewSettingsControl"; const LinkedLogo = memo(() => ( @@ -62,7 +62,7 @@ const openGrafanaInNewTab = () => { }; const SiteHeader = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const performAuth = usePerformAuth(); diff --git a/src/components/charts/PieChart.js b/src/components/charts/PieChart.js index f2684fbf7..9b9d308ba 100644 --- a/src/components/charts/PieChart.js +++ b/src/components/charts/PieChart.js @@ -1,11 +1,12 @@ import { useCallback, useMemo } from "react"; -import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import { PieChart as BentoPie } from "bento-charts"; import { setAutoQueryPageTransition } from "@/modules/explorer/actions"; +import { useAppDispatch } from "@/store"; + import ChartContainer from "./ChartContainer"; const PieChart = ({ @@ -18,7 +19,7 @@ const PieChart = ({ clickable = false, sortData = true, }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); const onAutoQueryTransition = useCallback( diff --git a/src/components/datasets/linked_field_set/LinkedFieldSetModal.js b/src/components/datasets/linked_field_set/LinkedFieldSetModal.js index b2afd6633..ec39aedd9 100644 --- a/src/components/datasets/linked_field_set/LinkedFieldSetModal.js +++ b/src/components/datasets/linked_field_set/LinkedFieldSetModal.js @@ -1,5 +1,4 @@ import { useCallback } from "react"; -import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; import { Form, Modal } from "antd"; @@ -8,13 +7,14 @@ import LinkedFieldSetForm from "./LinkedFieldSetForm"; import { FORM_MODE_ADD } from "@/constants"; import { addDatasetLinkedFieldSetIfPossible, saveDatasetLinkedFieldSetIfPossible } from "@/modules/metadata/actions"; +import { useProjects } from "@/modules/metadata/hooks"; import { useDataTypes } from "@/modules/services/hooks"; import { datasetPropTypesShape, linkedFieldSetPropTypesShape, propTypesFormMode } from "@/propTypes"; +import { useAppDispatch } from "@/store"; import { nop } from "@/utils/misc"; -import { useProjects } from "@/modules/metadata/hooks"; const LinkedFieldSetModal = ({ dataset, linkedFieldSetIndex, linkedFieldSet, mode, open, onCancel, onSubmit }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const [form] = Form.useForm(); diff --git a/src/components/discovery/DiscoveryQueryBuilder.js b/src/components/discovery/DiscoveryQueryBuilder.js index 60037535c..1c4622af8 100644 --- a/src/components/discovery/DiscoveryQueryBuilder.js +++ b/src/components/discovery/DiscoveryQueryBuilder.js @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { useSelector } from "react-redux"; import PropTypes from "prop-types"; import { Button, Card, Dropdown, Empty, Tabs, Typography } from "antd"; @@ -15,7 +14,7 @@ import { removeDataTypeQueryForm, } from "@/modules/explorer/actions"; import { useDataTypes, useServices } from "@/modules/services/hooks"; -import { useAppDispatch } from "@/store"; +import { useAppDispatch, useAppSelector } from "@/store"; import { nop } from "@/utils/misc"; import { OP_EQUALS } from "@/utils/search"; import { getFieldSchema } from "@/utils/schema"; @@ -28,18 +27,16 @@ const DiscoveryQueryBuilder = ({ activeDataset, dataTypeForms, requiredDataTypes const { isFetching: isFetchingServiceDataTypes, itemsByID: dataTypesByID } = useDataTypes(); const dataTypesByDataset = useDatasetDataTypes(); + const { isFetching: isFetchingServices } = useServices(); + + const { autoQuery, fetchingTextSearch } = useAppSelector((state) => state.explorer); - const autoQuery = useSelector((state) => state.explorer.autoQuery); // Mini state machine: when auto query is set: // 1. clear form(s) and set this to true; // 2. re-create forms and wait to receive ref; // 3. if this is true, and we have refs, execute part two of auto-query. const [shouldExecAutoQueryPt2, setShouldExecAutoQueryPt2] = useState(false); - const isFetchingTextSearch = useSelector((state) => state.explorer.fetchingTextSearch); - - const { isFetching: isFetchingServices } = useServices(); - const dataTypesLoading = isFetchingServices || isFetchingServiceDataTypes || dataTypesByDataset.isFetchingAll; const [schemasModalShown, setSchemasModalShown] = useState(false); @@ -274,7 +271,7 @@ const DiscoveryQueryBuilder = ({ activeDataset, dataTypeForms, requiredDataTypes type="primary" icon={} loading={searchLoading} - disabled={dataTypeForms.length === 0 || isFetchingTextSearch} + disabled={dataTypeForms.length === 0 || fetchingTextSearch} onClick={handleSubmit} > Search diff --git a/src/components/discovery/VariantSearchHeader.js b/src/components/discovery/VariantSearchHeader.js index 1baa6ef15..8c65b4b11 100644 --- a/src/components/discovery/VariantSearchHeader.js +++ b/src/components/discovery/VariantSearchHeader.js @@ -1,5 +1,4 @@ import { useState, useEffect, useCallback, useMemo } from "react"; -import { useSelector } from "react-redux"; import PropTypes from "prop-types"; import { Form, Input, Select } from "antd"; @@ -8,6 +7,7 @@ import LocusSearch from "./LocusSearch"; import { notAlleleCharactersRegex } from "@/utils/misc"; import { useGohanVariantsOverview } from "@/modules/explorer/hooks"; +import { useAppSelector } from "@/store"; const isValidLocus = (locus) => locus.chrom !== null && locus.start !== null && locus.end !== null; const normalizeAlleleText = (text) => text.toUpperCase().replaceAll(notAlleleCharactersRegex, ""); @@ -44,7 +44,7 @@ const VariantSearchHeader = ({ dataType, addVariantSearchValues }) => { const [activeAltValue, setActiveAltValue] = useState(null); const [assemblyId, setAssemblyId] = useState(overviewAssemblyIds.length === 1 ? overviewAssemblyIds[0] : null); const [locus, setLocus] = useState({ chrom: null, start: null, end: null }); - const isSubmitting = useSelector((state) => state.explorer.isSubmittingSearch); + const { isSubmittingSearch: isSubmitting } = useAppSelector((state) => state.explorer); // begin with required fields considered valid, so user isn't assaulted with error messages const [fieldsValidity, setFieldsValidity] = useState(INITIAL_FIELDS_VALIDITY); diff --git a/src/components/explorer/ExplorerDatasetSearch.js b/src/components/explorer/ExplorerDatasetSearch.js index ac9e74e6d..6c3c069d4 100644 --- a/src/components/explorer/ExplorerDatasetSearch.js +++ b/src/components/explorer/ExplorerDatasetSearch.js @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo } from "react"; -import { useSelector, useDispatch } from "react-redux"; import { useParams } from "react-router-dom"; import { Typography, Tabs } from "antd"; @@ -17,6 +16,7 @@ import { setActiveTab, } from "@/modules/explorer/actions"; import { useProjects } from "@/modules/metadata/hooks"; +import { useAppDispatch, useAppSelector } from "@/store"; import IndividualsTable from "./searchResultsTables/IndividualsTable"; import BiosamplesTable from "./searchResultsTables/BiosamplesTable"; @@ -36,15 +36,15 @@ const EMPTY_ARRAY = []; const ExplorerDatasetSearch = () => { const { dataset: datasetID } = useParams(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const datasetsByID = useProjects().datasetsByID; - const activeKey = useSelector((state) => state.explorer.activeTabByDatasetID[datasetID]) || TAB_KEYS.INDIVIDUAL; - const dataTypeForms = useSelector((state) => state.explorer.dataTypeFormsByDatasetID[datasetID]) ?? EMPTY_ARRAY; - const fetchingSearch = useSelector((state) => state.explorer.fetchingSearchByDatasetID[datasetID] || false); - const fetchingTextSearch = useSelector((state) => state.explorer.fetchingTextSearch || false); - const searchResults = useSelector((state) => state.explorer.searchResultsByDatasetID[datasetID] || null); + const activeKey = useAppSelector((state) => state.explorer.activeTabByDatasetID[datasetID]) || TAB_KEYS.INDIVIDUAL; + const dataTypeForms = useAppSelector((state) => state.explorer.dataTypeFormsByDatasetID[datasetID]) ?? EMPTY_ARRAY; + const fetchingSearch = useAppSelector((state) => state.explorer.fetchingSearchByDatasetID[datasetID] || false); + const fetchingTextSearch = useAppSelector((state) => state.explorer.fetchingTextSearch || false); + const searchResults = useAppSelector((state) => state.explorer.searchResultsByDatasetID[datasetID] || null); useEffect(() => { console.debug("search results: ", searchResults); diff --git a/src/components/explorer/ExplorerSearchContent.js b/src/components/explorer/ExplorerSearchContent.js index de616d5f0..1ffb3098a 100644 --- a/src/components/explorer/ExplorerSearchContent.js +++ b/src/components/explorer/ExplorerSearchContent.js @@ -1,10 +1,10 @@ import { useMemo } from "react"; -import { useSelector } from "react-redux"; import { Navigate, Route, Routes } from "react-router-dom"; import { Layout, Menu, Skeleton } from "antd"; import { useDatasetsArray, useProjectsArray } from "@/modules/metadata/hooks"; +import { useAppSelector } from "@/store"; import { LAYOUT_CONTENT_STYLE } from "@/styles/layoutContent"; import { matchingMenuKeys, transformMenuItem } from "@/utils/menu"; @@ -13,7 +13,7 @@ import SitePageHeader from "../SitePageHeader"; const ExplorerSearchContent = () => { const projects = useProjectsArray(); - const isFetchingDependentData = useSelector((state) => state.user.isFetchingDependentData); + const { isFetchingDependentData } = useAppSelector((state) => state.user); const menuItems = useMemo( () => diff --git a/src/components/explorer/ExplorerSearchResultsTable.js b/src/components/explorer/ExplorerSearchResultsTable.js index 02628b127..b2591807a 100644 --- a/src/components/explorer/ExplorerSearchResultsTable.js +++ b/src/components/explorer/ExplorerSearchResultsTable.js @@ -1,7 +1,6 @@ import { useState, useMemo, useCallback } from "react"; import PropTypes from "prop-types"; import { useParams } from "react-router-dom"; -import { useSelector, useDispatch } from "react-redux"; import { Table, Typography, Button, Spin } from "antd"; import { BarChartOutlined, ExportOutlined } from "@ant-design/icons"; @@ -16,6 +15,7 @@ import { performExperimentsDownloadCSVIfPossible, setTableSortOrder, } from "@/modules/explorer/actions"; +import { useAppDispatch, useAppSelector } from "@/store"; const PAGE_SIZE = 25; @@ -51,11 +51,11 @@ const ExplorerSearchResultsTable = ({ return `Showing results ${start}-${end} of ${filteredData.length}`; }, [currentPage, filteredData]); - const searchResults = useSelector((state) => state.explorer.searchResultsByDatasetID[dataset] || null); - const selectedRows = useSelector((state) => state.explorer.selectedRowsByDatasetID[dataset]); - const isFetchingDownload = useSelector((state) => state.explorer.isFetchingDownload || false); - const fetchingSearch = useSelector((state) => state.explorer.fetchingSearchByDatasetID[dataset] || false); - const dispatch = useDispatch(); + const searchResults = useAppSelector((state) => state.explorer.searchResultsByDatasetID[dataset] || null); + const selectedRows = useAppSelector((state) => state.explorer.selectedRowsByDatasetID[dataset]); + const isFetchingDownload = useAppSelector((state) => state.explorer.isFetchingDownload || false); + const fetchingSearch = useAppSelector((state) => state.explorer.fetchingSearchByDatasetID[dataset] || false); + const dispatch = useAppDispatch(); const handleSetSelectedRows = useCallback( (...args) => dispatch(setSelectedRows(dataset, ...args)), diff --git a/src/components/explorer/IndividualExperiments.js b/src/components/explorer/IndividualExperiments.js index 94e8ec7aa..28e4bae13 100644 --- a/src/components/explorer/IndividualExperiments.js +++ b/src/components/explorer/IndividualExperiments.js @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { Link, useParams } from "react-router-dom"; import PropTypes from "prop-types"; @@ -9,6 +8,7 @@ import { BarsOutlined, EyeOutlined, FileTextOutlined, ProfileOutlined } from "@a import { EM_DASH } from "@/constants"; import { experimentPropTypesShape, experimentResultPropTypesShape, individualPropTypesShape } from "@/propTypes"; import { getFileDownloadUrlsFromDrs } from "@/modules/drs/actions"; +import { useAppDispatch, useAppSelector } from "@/store"; import { guessFileType } from "@/utils/files"; import { useDeduplicatedIndividualBiosamples } from "./utils"; @@ -32,7 +32,7 @@ const VIEWABLE_FILE_FORMATS = ["PDF", "CSV", "TSV"]; const ExperimentResultActions = ({ result }) => { const { filename } = result; - const downloadUrls = useSelector((state) => state.drs.downloadUrlsByFilename); + const downloadUrls = useAppSelector((state) => state.drs.downloadUrlsByFilename); const url = downloadUrls[filename]?.url; const [viewModalVisible, setViewModalVisible] = useState(false); @@ -268,7 +268,7 @@ const EXPERIMENT_COLUMNS = [ ]; const Experiments = ({ individual, handleExperimentClick }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { selectedExperiment } = useParams(); diff --git a/src/components/explorer/IndividualGenes.js b/src/components/explorer/IndividualGenes.js index 05fb4bb56..734ae530a 100644 --- a/src/components/explorer/IndividualGenes.js +++ b/src/components/explorer/IndividualGenes.js @@ -1,6 +1,5 @@ import { memo, useContext, useMemo } from "react"; import { Link } from "react-router-dom"; -import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; import { Button, Descriptions, List, Table, Typography } from "antd"; @@ -8,6 +7,7 @@ import { Button, Descriptions, List, Table, Typography } from "antd"; import { ExplorerIndividualContext } from "./contexts/individual"; import { setIgvPosition } from "@/modules/explorer/actions"; import { individualPropTypesShape } from "@/propTypes"; +import { useAppDispatch } from "@/store"; import { explorerIndividualUrl } from "./utils"; @@ -32,7 +32,7 @@ StringList.propTypes = { }; export const GeneDescriptor = ({ geneDescriptor }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { individualID } = useContext(ExplorerIndividualContext); const tracksUrl = useMemo(() => { if (individualID) { @@ -66,7 +66,7 @@ GeneDescriptor.propTypes = { }; const GeneIGVLink = memo(({ symbol, tracksUrl }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); return ( dispatch(setIgvPosition(symbol))} to={tracksUrl}> diff --git a/src/components/explorer/IndividualPhenopackets.js b/src/components/explorer/IndividualPhenopackets.js index ff7139503..aa4b3c5ba 100644 --- a/src/components/explorer/IndividualPhenopackets.js +++ b/src/components/explorer/IndividualPhenopackets.js @@ -1,5 +1,4 @@ import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { Divider, Skeleton } from "antd"; @@ -9,16 +8,17 @@ import { individualPropTypesShape } from "@/propTypes"; import DownloadButton from "@/components/common/DownloadButton"; import JsonView from "@/components/common/JsonView"; import { useService } from "@/modules/services/hooks"; +import { useAppDispatch, useAppSelector } from "@/store"; const IndividualPhenopackets = ({ individual }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { id: individualId } = individual; const katsuUrl = useService("metadata")?.url ?? ""; const downloadUrl = `${katsuUrl}/api/individuals/${individualId}/phenopackets?attachment=1&format=json`; - const phenopacketsByIndividualID = useSelector((state) => state.individuals.phenopacketsByIndividualID); + const { phenopacketsByIndividualID } = useAppSelector((state) => state.individuals); const { isFetching, data } = phenopacketsByIndividualID[individualId] ?? {}; diff --git a/src/components/explorer/IndividualTracks.js b/src/components/explorer/IndividualTracks.js index 453057a5e..0840b5b2a 100644 --- a/src/components/explorer/IndividualTracks.js +++ b/src/components/explorer/IndividualTracks.js @@ -1,5 +1,4 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; import PropTypes from "prop-types"; import { debounce } from "lodash"; @@ -17,8 +16,10 @@ import { setIgvPosition } from "@/modules/explorer/actions"; import { useIgvGenomes } from "@/modules/explorer/hooks"; import { useReferenceGenomes } from "@/modules/reference/hooks"; import { useService } from "@/modules/services/hooks"; +import { useAppDispatch, useAppSelector } from "@/store"; import { guessFileType } from "@/utils/files"; import { simpleDeepCopy } from "@/utils/misc"; + import { useDeduplicatedIndividualBiosamples } from "./utils"; const SQUISHED_CALL_HEIGHT = 10; @@ -154,15 +155,15 @@ const IndividualTracks = ({ individual }) => { const igvBrowserRef = useRef(null); const [creatingIgvBrowser, setCreatingIgvBrowser] = useState(false); - const { igvUrlsByFilename: igvUrls, isFetchingIgvUrls } = useSelector((state) => state.drs); + const { igvUrlsByFilename: igvUrls, isFetchingIgvUrls } = useAppSelector((state) => state.drs); // read stored position only on first render - const igvPosition = useSelector( - (state) => state.explorer.igvPosition, + const { igvPosition } = useAppSelector( + (state) => state.explorer, () => true, // We don't want to re-render anything when the position changes ); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const referenceService = useService("reference"); // Built-in igv.js genomes (with annotations): diff --git a/src/components/explorer/searchResultsTables/BiosamplesTable.js b/src/components/explorer/searchResultsTables/BiosamplesTable.js index e995f1db2..0707b61fb 100644 --- a/src/components/explorer/searchResultsTables/BiosamplesTable.js +++ b/src/components/explorer/searchResultsTables/BiosamplesTable.js @@ -1,6 +1,7 @@ import PropTypes from "prop-types"; + +import { useAppSelector } from "@/store"; import { useSortedColumns } from "../hooks/explorerHooks"; -import { useSelector } from "react-redux"; import BiosampleIDCell from "./BiosampleIDCell"; import ExplorerSearchResultsTable from "../ExplorerSearchResultsTable"; @@ -132,7 +133,7 @@ const BIOSAMPLES_COLUMNS = [ ]; const BiosamplesTable = ({ data, datasetID }) => { - const tableSortOrder = useSelector((state) => state.explorer.tableSortOrderByDatasetID[datasetID]?.["biosamples"]); + const tableSortOrder = useAppSelector((state) => state.explorer.tableSortOrderByDatasetID[datasetID]?.["biosamples"]); const { sortedData, columnsWithSortOrder } = useSortedColumns(data, tableSortOrder, BIOSAMPLES_COLUMNS); diff --git a/src/components/explorer/searchResultsTables/ExperimentsTable.js b/src/components/explorer/searchResultsTables/ExperimentsTable.js index 2a7112e81..08dc5f2fc 100644 --- a/src/components/explorer/searchResultsTables/ExperimentsTable.js +++ b/src/components/explorer/searchResultsTables/ExperimentsTable.js @@ -1,8 +1,8 @@ import { memo, useMemo } from "react"; import { Link, useLocation } from "react-router-dom"; -import { useSelector } from "react-redux"; import PropTypes from "prop-types"; +import { useAppSelector } from "@/store"; import { useSortedColumns, useDynamicTableFilterOptions } from "../hooks/explorerHooks"; import { explorerIndividualUrl } from "../utils"; @@ -29,7 +29,9 @@ ExperimentRender.propTypes = { }; const ExperimentsTable = ({ data, datasetID }) => { - const tableSortOrder = useSelector((state) => state.explorer.tableSortOrderByDatasetID[datasetID]?.["experiments"]); + const tableSortOrder = useAppSelector( + (state) => state.explorer.tableSortOrderByDatasetID[datasetID]?.["experiments"], + ); const experimentTypeFilters = useDynamicTableFilterOptions(data, "experimentType"); diff --git a/src/components/explorer/searchResultsTables/IndividualsTable.js b/src/components/explorer/searchResultsTables/IndividualsTable.js index c7880d8b6..9dbecf3ea 100644 --- a/src/components/explorer/searchResultsTables/IndividualsTable.js +++ b/src/components/explorer/searchResultsTables/IndividualsTable.js @@ -1,7 +1,7 @@ import { Fragment } from "react"; import PropTypes from "prop-types"; +import { useAppSelector } from "@/store"; import { useSortedColumns } from "../hooks/explorerHooks"; -import { useSelector } from "react-redux"; import ExplorerSearchResultsTable from "../ExplorerSearchResultsTable"; import BiosampleIDCell from "./BiosampleIDCell"; import IndividualIDCell from "./IndividualIDCell"; @@ -46,7 +46,9 @@ const SEARCH_RESULT_COLUMNS = [ ]; const IndividualsTable = ({ data, datasetID }) => { - const tableSortOrder = useSelector((state) => state.explorer.tableSortOrderByDatasetID[datasetID]?.["individuals"]); + const tableSortOrder = useAppSelector( + (state) => state.explorer.tableSortOrderByDatasetID[datasetID]?.["individuals"], + ); const { sortedData, columnsWithSortOrder } = useSortedColumns(data, tableSortOrder, SEARCH_RESULT_COLUMNS); diff --git a/src/components/explorer/utils.js b/src/components/explorer/utils.js index a5e90143f..97f667382 100644 --- a/src/components/explorer/utils.js +++ b/src/components/explorer/utils.js @@ -1,6 +1,6 @@ import { useEffect, useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { fetchDatasetResourcesIfNecessary } from "@/modules/datasets/actions"; +import { useAppDispatch, useAppSelector } from "@/store"; export const useDeduplicatedIndividualBiosamples = (individual) => useMemo( @@ -82,9 +82,9 @@ export const useIndividualGeneDescriptors = (individual) => { }; export const useDatasetResources = (datasetIDOrDatasetIDs) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); - const datasetResources = useSelector((state) => state.datasetResources.itemsByID); + const datasetResources = useAppSelector((state) => state.datasetResources.itemsByID); const datasetIDs = useMemo( () => (Array.isArray(datasetIDOrDatasetIDs) ? datasetIDOrDatasetIDs : [datasetIDOrDatasetIDs]), diff --git a/src/components/manager/ActionContainer.js b/src/components/manager/ActionContainer.js deleted file mode 100644 index 88dccd900..000000000 --- a/src/components/manager/ActionContainer.js +++ /dev/null @@ -1,24 +0,0 @@ -import PropTypes from "prop-types"; - -const style = { - display: "flex", - gap: "12px", - alignItems: "baseline", - position: "sticky", - paddingBottom: 4, - backgroundColor: "white", - boxShadow: "0 10px 10px white, 0 -10px 0 white", - top: 8, - zIndex: 10, -}; - -const ActionContainer = ({ children, ...props }) => ( -
- {children} -
-); -ActionContainer.propTypes = { - children: PropTypes.node, -}; - -export default ActionContainer; diff --git a/src/components/manager/ActionContainer.tsx b/src/components/manager/ActionContainer.tsx new file mode 100644 index 000000000..8f240e862 --- /dev/null +++ b/src/components/manager/ActionContainer.tsx @@ -0,0 +1,26 @@ +import type { CSSProperties, ReactNode } from "react"; + +const BASE_STYLE: CSSProperties = { + display: "flex", + gap: "12px", + alignItems: "baseline", + position: "sticky", + paddingBottom: 4, + backgroundColor: "white", + boxShadow: "0 10px 10px white, 0 -10px 0 white", + top: 8, + zIndex: 10, +}; + +type ActionContainerProps = { + children: ReactNode; + style?: CSSProperties; +}; + +const ActionContainer = ({ children, style, ...props }: ActionContainerProps) => ( +
+ {children} +
+); + +export default ActionContainer; diff --git a/src/components/manager/ManagerAnalysisContent.js b/src/components/manager/ManagerAnalysisContent.js index 926537e33..7fa307b2b 100644 --- a/src/components/manager/ManagerAnalysisContent.js +++ b/src/components/manager/ManagerAnalysisContent.js @@ -1,17 +1,17 @@ -import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { analyzeData, RESOURCE_EVERYTHING } from "bento-auth-js"; import { useResourcePermissionsWrapper } from "@/hooks"; import { submitAnalysisWorkflowRun } from "@/modules/wes/actions"; +import { useAppDispatch } from "@/store"; import ForbiddenContent from "../ForbiddenContent"; import RunSetupWizard from "./RunSetupWizard"; import RunSetupConfirmDisplay from "./RunSetupConfirmDisplay"; const ManagerAnalysisContent = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); const { permissions, hasAttemptedPermissions } = useResourcePermissionsWrapper(RESOURCE_EVERYTHING); diff --git a/src/components/manager/ManagerExportContent.js b/src/components/manager/ManagerExportContent.js index c067a15fe..4444acb0e 100644 --- a/src/components/manager/ManagerExportContent.js +++ b/src/components/manager/ManagerExportContent.js @@ -1,17 +1,17 @@ -import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { exportData, RESOURCE_EVERYTHING } from "bento-auth-js"; import { useResourcePermissionsWrapper } from "@/hooks"; import { submitExportWorkflowRun } from "@/modules/wes/actions"; +import { useAppDispatch } from "@/store"; import ForbiddenContent from "../ForbiddenContent"; import RunSetupWizard from "./RunSetupWizard"; import RunSetupConfirmDisplay from "./RunSetupConfirmDisplay"; const ManagerExportContent = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); const { permissions, hasAttemptedPermissions } = useResourcePermissionsWrapper(RESOURCE_EVERYTHING); diff --git a/src/components/manager/ManagerIngestionContent.js b/src/components/manager/ManagerIngestionContent.js index 230e2066d..132799742 100644 --- a/src/components/manager/ManagerIngestionContent.js +++ b/src/components/manager/ManagerIngestionContent.js @@ -1,4 +1,3 @@ -import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { ingestData, ingestReferenceMaterial, RESOURCE_EVERYTHING } from "bento-auth-js"; @@ -9,9 +8,10 @@ import { submitIngestionWorkflowRun } from "@/modules/wes/actions"; import ForbiddenContent from "../ForbiddenContent"; import RunSetupWizard from "./RunSetupWizard"; import RunSetupConfirmDisplay from "./RunSetupConfirmDisplay"; +import { useAppDispatch } from "@/store"; const ManagerIngestionContent = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); const { permissions, hasAttemptedPermissions } = useResourcePermissionsWrapper(RESOURCE_EVERYTHING); diff --git a/src/components/manager/RunSetupConfirmDisplay.js b/src/components/manager/RunSetupConfirmDisplay.js index 92c71ccec..142ff7e15 100644 --- a/src/components/manager/RunSetupConfirmDisplay.js +++ b/src/components/manager/RunSetupConfirmDisplay.js @@ -1,8 +1,9 @@ -import { useSelector } from "react-redux"; import PropTypes from "prop-types"; import { Button, Form, List } from "antd"; +import { useRuns } from "@/modules/wes/hooks"; + import WorkflowListItem from "./WorkflowListItem"; import RunSetupInputsTable from "./RunSetupInputsTable"; import { FORM_BUTTON_COL, FORM_LABEL_COL, FORM_WRAPPER_COL } from "./workflowCommon"; @@ -14,7 +15,7 @@ const styles = { }; const RunSetupConfirmDisplay = ({ selectedWorkflow, inputs, handleRunWorkflow, runButtonText }) => { - const isSubmittingRun = useSelector((state) => state.runs.isSubmittingRun); + const { isSubmittingRun } = useRuns(); return (
diff --git a/src/components/manager/drs/ManagerDRSContent.js b/src/components/manager/drs/ManagerDRSContent.js index 44f70dfb0..ca1b4a720 100644 --- a/src/components/manager/drs/ManagerDRSContent.js +++ b/src/components/manager/drs/ManagerDRSContent.js @@ -1,5 +1,4 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { useSearchParams } from "react-router-dom"; import PropTypes from "prop-types"; @@ -20,6 +19,7 @@ import { useResourcePermissionsWrapper } from "@/hooks"; import { clearDRSObjectSearch, deleteDRSObject, performDRSObjectSearch } from "@/modules/drs/actions"; import { useProjects } from "@/modules/metadata/hooks"; import { useService } from "@/modules/services/hooks"; +import { useAppDispatch, useAppSelector } from "@/store"; import { LAYOUT_CONTENT_STYLE } from "@/styles/layoutContent"; import DatasetTitleDisplay from "../DatasetTitleDisplay"; @@ -125,7 +125,7 @@ const DRSObjectDeleteWarningParagraph = memo(({ plural }) => ( DRSObjectDeleteWarningParagraph.propTypes = { plural: PropTypes.bool }; const DRSObjectDeleteButton = ({ drsObject, disabled }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const onClick = useCallback(() => { Modal.confirm({ @@ -161,7 +161,7 @@ const DRS_TABLE_EXPANDABLE = { }; const ManagerDRSContent = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { itemsByID: projectsByID, datasetsByID } = useProjects(); @@ -184,7 +184,7 @@ const ManagerDRSContent = () => { objectSearchResults: rawObjectResults, objectSearchIsFetching, objectSearchAttempted, - } = useSelector((state) => state.drs); + } = useAppSelector((state) => state.drs); const objectResults = useMemo( () => diff --git a/src/components/manager/projects/ManagerProjectDatasetContent.js b/src/components/manager/projects/ManagerProjectDatasetContent.js index f49b2f5d1..ce4a66182 100644 --- a/src/components/manager/projects/ManagerProjectDatasetContent.js +++ b/src/components/manager/projects/ManagerProjectDatasetContent.js @@ -1,12 +1,13 @@ import { useCallback, useMemo } from "react"; import { Navigate, Route, Routes } from "react-router-dom"; -import { useDispatch, useSelector } from "react-redux"; import { Button, Empty, Layout, Menu, Typography } from "antd"; import { PlusOutlined, ReloadOutlined } from "@ant-design/icons"; import { createProject, RESOURCE_EVERYTHING } from "bento-auth-js"; +import ForbiddenContent from "@/components/ForbiddenContent"; +import ServiceError from "@/components/common/ServiceError"; import ProjectCreationModal from "./ProjectCreationModal"; import ProjectSkeleton from "./ProjectSkeleton"; import RoutedProject from "./RoutedProject"; @@ -19,8 +20,7 @@ import { LAYOUT_CONTENT_STYLE } from "@/styles/layoutContent"; import { matchingMenuKeys, transformMenuItem } from "@/utils/menu"; import { useServices } from "@/modules/services/hooks"; import { useCanManageAtLeastOneProjectOrDataset } from "@/modules/authz/hooks"; -import ForbiddenContent from "@/components/ForbiddenContent"; -import ServiceError from "@/components/common/ServiceError"; +import { useAppDispatch, useAppSelector } from "@/store"; const styles = { projectHelpText: { @@ -37,7 +37,7 @@ const styles = { }; const ManagerProjectDatasetContent = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { hasPermission: canManageProjectsDatasets, @@ -49,7 +49,7 @@ const ManagerProjectDatasetContent = () => { useHasResourcePermissionWrapper(RESOURCE_EVERYTHING, createProject); const { items, isFetching: isFetchingProjects } = useProjects(); - const { isFetchingDependentData } = useSelector((state) => state.user); + const { isFetchingDependentData } = useAppSelector((state) => state.user); const { metadataService, isFetchingAll: isFetchingAllServices } = useServices(); diff --git a/src/components/manager/projects/ProjectJsonSchema.js b/src/components/manager/projects/ProjectJsonSchema.js index 6efcaabf0..1eef694fd 100644 --- a/src/components/manager/projects/ProjectJsonSchema.js +++ b/src/components/manager/projects/ProjectJsonSchema.js @@ -1,5 +1,4 @@ import { useCallback } from "react"; -import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; import { Button, Card, Descriptions, Modal, Typography } from "antd"; @@ -8,6 +7,7 @@ import { DeleteOutlined } from "@ant-design/icons"; import JsonView from "@/components/common/JsonView"; import { deleteProjectJsonSchema } from "@/modules/metadata/actions"; import { projectJsonSchemaTypesShape } from "@/propTypes"; +import { useAppDispatch } from "@/store"; // Custom style based on Typography.Text in 'code' mode, with colors for dark backgrounds const CODE_STYLE = { @@ -34,7 +34,7 @@ ExtraPropertiesCode.propTypes = { }; const ProjectJsonSchema = ({ projectSchema }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const handleDelete = useCallback(() => { const deleteModal = Modal.confirm({ diff --git a/src/components/manager/projects/ProjectJsonSchemaModal.js b/src/components/manager/projects/ProjectJsonSchemaModal.js index 950d50855..a849b094c 100644 --- a/src/components/manager/projects/ProjectJsonSchemaModal.js +++ b/src/components/manager/projects/ProjectJsonSchemaModal.js @@ -1,5 +1,4 @@ import { useCallback } from "react"; -import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; import { Button, Form, Modal } from "antd"; @@ -7,11 +6,12 @@ import { PlusOutlined } from "@ant-design/icons"; import { createProjectJsonSchema } from "@/modules/metadata/actions"; import { useProjectJsonSchemaTypes } from "@/modules/metadata/hooks"; +import { useAppDispatch } from "@/store"; import ProjectJsonSchemaForm from "./ProjectJsonSchemaForm"; const ProjectJsonSchemaModal = ({ projectId, open, onOk, onCancel }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { isFetchingExtraPropertiesSchemaTypes, isCreatingJsonSchema, extraPropertiesSchemaTypes } = useProjectJsonSchemaTypes(); diff --git a/src/components/manager/runs/RunDetailContent.tsx b/src/components/manager/runs/RunDetailContent.tsx index 356c1a8e8..d401acdb0 100644 --- a/src/components/manager/runs/RunDetailContent.tsx +++ b/src/components/manager/runs/RunDetailContent.tsx @@ -4,7 +4,6 @@ import { Navigate, Route, Routes, useNavigate, useParams } from "react-router-do import { Result, Skeleton } from "antd"; import { useRuns } from "@/modules/wes/hooks"; -import type { WorkflowRunWithNestedDetailsState } from "@/modules/wes/types"; import Run from "./Run"; @@ -14,7 +13,7 @@ const RunDetailContentInner = () => { const { itemsByID: runsByID, isFetching, hasAttempted } = useRuns(); - const run = runsByID[id] as WorkflowRunWithNestedDetailsState | undefined; + const run = id ? runsByID[id] : undefined; const loading = isFetching || !!run?.isFetching; const onChangeTab = useCallback((key: string) => navigate(`../${key}`), [navigate]); diff --git a/src/components/notifications/NotificationDrawer.js b/src/components/notifications/NotificationDrawer.js index c9b29ccf0..c48dd8f76 100644 --- a/src/components/notifications/NotificationDrawer.js +++ b/src/components/notifications/NotificationDrawer.js @@ -1,5 +1,4 @@ import { memo, useCallback } from "react"; -import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { Button, Divider, Drawer } from "antd"; @@ -8,9 +7,10 @@ import NotificationList from "./NotificationList"; import { hideNotificationDrawer, markAllNotificationsAsRead } from "@/modules/notifications/actions"; import { useNotifications } from "@/modules/notifications/hooks"; +import { useAppDispatch } from "@/store"; const NotificationDrawer = memo(() => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); const markAllAsRead = useCallback(() => { diff --git a/src/components/notifications/NotificationList.js b/src/components/notifications/NotificationList.js index d81143036..0c902fed7 100644 --- a/src/components/notifications/NotificationList.js +++ b/src/components/notifications/NotificationList.js @@ -1,5 +1,4 @@ import { useCallback, useMemo } from "react"; -import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; @@ -10,6 +9,7 @@ import { markNotificationAsRead } from "@/modules/notifications/actions"; import { useNotifications } from "@/modules/notifications/hooks"; import { useServices } from "@/modules/services/hooks"; import { notificationPropTypesShape } from "@/propTypes"; +import { useAppDispatch } from "@/store"; import { NOTIFICATION_WES_RUN_COMPLETED, NOTIFICATION_WES_RUN_FAILED, navigateToWESRun } from "@/utils/notifications"; const sortNotificationTimestamps = (a, b) => b.timestamp - a.timestamp; @@ -23,7 +23,7 @@ const notificationTimestampStyle = { }; const NotificationList = ({ notifications, small }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const navigate = useNavigate(); const fetchingNotifications = useNotifications().isFetching; diff --git a/src/components/overview/OverviewSettingsControl.js b/src/components/overview/OverviewSettingsControl.js index 290844178..8161575a4 100644 --- a/src/components/overview/OverviewSettingsControl.js +++ b/src/components/overview/OverviewSettingsControl.js @@ -1,10 +1,10 @@ import { useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { Col, InputNumber, Row, Slider, Modal } from "antd"; import PropTypes from "prop-types"; +import { DEFAULT_OTHER_THRESHOLD_PERCENTAGE } from "@/constants"; import { setOtherThresholdPercentage } from "@/modules/explorer/actions"; +import { useAppDispatch, useAppSelector } from "@/store"; import { writeToLocalStorage } from "@/utils/localStorageUtils"; -import { DEFAULT_OTHER_THRESHOLD_PERCENTAGE } from "@/constants"; // currently only sets pie chart "Other" threshold: // writes to redux store on all changes, writes to redux and localStorage on close @@ -17,14 +17,14 @@ const toolTipFormatter = (value) => `${value}%`; const sliderTooltip = { formatter: toolTipFormatter }; const OverviewSettingsControl = ({ modalVisible, toggleModalVisibility }) => { - const otherThresholdPercentage = useSelector((state) => state.explorer.otherThresholdPercentage); + const otherThresholdPercentage = useAppSelector((state) => state.explorer.otherThresholdPercentage); const [inputValue, setInputValue] = useState(otherThresholdPercentage ?? DEFAULT_OTHER_THRESHOLD_PERCENTAGE); //preserve earlier setting in case user cancels const [previousThreshold, setPreviousThreshold] = useState( otherThresholdPercentage ?? DEFAULT_OTHER_THRESHOLD_PERCENTAGE, ); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const handleChange = (newValue) => { setInputValue(newValue); diff --git a/src/components/services/ServiceRequestModal.tsx b/src/components/services/ServiceRequestModal.tsx index 2cc08505e..a108bc04b 100644 --- a/src/components/services/ServiceRequestModal.tsx +++ b/src/components/services/ServiceRequestModal.tsx @@ -15,7 +15,10 @@ type ServiceRequestModalProps = { const ServiceRequestModal = ({ service, onCancel }: ServiceRequestModalProps) => { const bentoServicesByKind = useBentoServices().itemsByKind; - const serviceUrl = useMemo(() => bentoServicesByKind[service]?.url, [bentoServicesByKind, service]); + const serviceUrl = useMemo( + () => (service ? bentoServicesByKind[service]?.url : undefined), + [bentoServicesByKind, service], + ); const [requestPath, setRequestPath] = useState("service-info"); const [requestLoading, setRequestLoading] = useState(false); diff --git a/src/modules/explorer/hooks.js b/src/modules/explorer/hooks.js index 26ba8994f..79031035e 100644 --- a/src/modules/explorer/hooks.js +++ b/src/modules/explorer/hooks.js @@ -1,8 +1,7 @@ import { useEffect } from "react"; -import { useSelector } from "react-redux"; import { useService } from "@/modules/services/hooks"; -import { useAppDispatch } from "@/store"; +import { useAppDispatch, useAppSelector } from "@/store"; import { fetchIgvGenomes, performGetGohanVariantsOverviewIfPossible } from "./actions"; @@ -11,7 +10,7 @@ export const useIgvGenomes = () => { useEffect(() => { dispatch(fetchIgvGenomes()); }, [dispatch]); - return useSelector((state) => state.igvGenomes); + return useAppSelector((state) => state.igvGenomes); }; export const useGohanVariantsOverview = () => { @@ -24,8 +23,9 @@ export const useGohanVariantsOverview = () => { } }, [dispatch, gohan]); - const data = useSelector((state) => state.explorer.variantsOverviewResponse); - const isFetching = useSelector((state) => state.explorer.fetchingVariantsOverview); + const { variantsOverviewResponse: data, fetchingVariantsOverview: isFetching } = useAppSelector( + (state) => state.explorer, + ); return { data, isFetching }; }; diff --git a/src/modules/manager/hooks.js b/src/modules/manager/hooks.js index 156fbf842..adac084b3 100644 --- a/src/modules/manager/hooks.js +++ b/src/modules/manager/hooks.js @@ -1,14 +1,14 @@ import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { fetchIndividual } from "@/modules/metadata/actions"; import { useService } from "@/modules/services/hooks"; +import { useAppDispatch, useAppSelector } from "@/store"; export const useIndividual = (individualID) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const metadataService = useService("metadata"); - const individuals = useSelector((state) => state.individuals.itemsByID); + const individuals = useAppSelector((state) => state.individuals.itemsByID); useEffect(() => { if (metadataService && individualID) { diff --git a/src/modules/metadata/hooks.js b/src/modules/metadata/hooks.js index 4058524b3..fbb0394a0 100644 --- a/src/modules/metadata/hooks.js +++ b/src/modules/metadata/hooks.js @@ -1,8 +1,7 @@ import { useEffect, useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { makeProjectDatasetResource, makeProjectResource } from "bento-auth-js"; import { useService } from "@/modules/services/hooks"; -import { useAppSelector } from "@/store"; +import { useAppDispatch, useAppSelector } from "@/store"; import { fetchDiscoverySchema, fetchExtraPropertiesSchemaTypes, @@ -12,12 +11,12 @@ import { import { useJsonSchemaValidator } from "@/hooks"; export const useProjects = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const metadataService = useService("metadata"); useEffect(() => { dispatch(fetchProjectsWithDatasets()).catch((err) => console.error(err)); }, [dispatch, metadataService]); - return useSelector((state) => state.projects); + return useAppSelector((state) => state.projects); }; export const useProjectsArray = () => useProjects().items; @@ -36,16 +35,16 @@ export const useProjectsAndDatasetsAsAuthzResources = () => { }; export const useOverviewSummary = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const metadataService = useService("metadata"); useEffect(() => { dispatch(fetchOverviewSummaryIfNeeded()).catch((err) => console.error(err)); }, [dispatch, metadataService]); - return useSelector((state) => state.overviewSummary); + return useAppSelector((state) => state.overviewSummary); }; export const useProjectJsonSchemaTypes = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const metadataService = useService("metadata"); useEffect(() => { dispatch(fetchExtraPropertiesSchemaTypes()); @@ -62,7 +61,7 @@ export const useProjectJsonSchemaTypes = () => { }; export const useDiscoverySchema = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const metadataService = useService("metadata"); useEffect(() => { dispatch(fetchDiscoverySchema()).catch((err) => console.error(err)); diff --git a/src/modules/notifications/hooks.js b/src/modules/notifications/hooks.js index 5660f4b4d..e076bb586 100644 --- a/src/modules/notifications/hooks.js +++ b/src/modules/notifications/hooks.js @@ -1,13 +1,13 @@ import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { RESOURCE_EVERYTHING, viewNotifications } from "bento-auth-js"; import { useHasResourcePermissionWrapper } from "@/hooks"; import { useService } from "@/modules/services/hooks"; +import { useAppDispatch, useAppSelector } from "@/store"; import { fetchNotifications } from "./actions"; export const useNotifications = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const service = useService("notification"); @@ -20,5 +20,5 @@ export const useNotifications = () => { } }, [dispatch, service, hasPermission]); - return useSelector((state) => state.notifications); + return useAppSelector((state) => state.notifications); }; diff --git a/src/modules/reference/hooks.js b/src/modules/reference/hooks.js index 4b04b3593..6ead66e9c 100644 --- a/src/modules/reference/hooks.js +++ b/src/modules/reference/hooks.js @@ -1,18 +1,19 @@ import { useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { useAuthorizationHeader } from "bento-auth-js"; import { useService } from "@/modules/services/hooks"; +import { useAppDispatch, useAppSelector } from "@/store"; + import { fetchReferenceGenomesIfNeeded } from "./actions"; export const useReferenceGenomes = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const referenceService = useService("reference"); useEffect(() => { dispatch(fetchReferenceGenomesIfNeeded()); }, [dispatch, referenceService]); - return useSelector((state) => state.referenceGenomes); + return useAppSelector((state) => state.referenceGenomes); }; /** diff --git a/src/modules/services/hooks.js b/src/modules/services/hooks.js index 44ce6331f..78b049c0a 100644 --- a/src/modules/services/hooks.js +++ b/src/modules/services/hooks.js @@ -1,14 +1,13 @@ import { useEffect, useMemo } from "react"; -import { useSelector } from "react-redux"; import { fetchBentoServices, fetchDataTypes, fetchServices } from "./actions"; -import { useAppDispatch } from "@/store"; +import { useAppDispatch, useAppSelector } from "@/store"; export const useBentoServices = () => { const dispatch = useAppDispatch(); useEffect(() => { dispatch(fetchBentoServices()).catch((err) => console.error(err)); }, [dispatch]); - return useSelector((state) => state.bentoServices); + return useAppSelector((state) => state.bentoServices); }; export const useBentoService = (kind) => { @@ -21,7 +20,7 @@ export const useServices = () => { useEffect(() => { dispatch(fetchServices()).catch((err) => console.error(err)); }, [dispatch]); - return useSelector((state) => state.services); // From service registry; service-info style + return useAppSelector((state) => state.services); // From service registry; service-info style }; export const useService = (kind) => { @@ -34,12 +33,12 @@ export const useDataTypes = () => { useEffect(() => { dispatch(fetchDataTypes()).catch((err) => console.error(err)); }, [dispatch]); - return useSelector((state) => state.serviceDataTypes); + return useAppSelector((state) => state.serviceDataTypes); }; export const useWorkflows = () => { const isFetchingAllServices = useServices().isFetchingAll; - const { isFetching: isFetchingServiceWorkflows, items: serviceWorkflows } = useSelector( + const { isFetching: isFetchingServiceWorkflows, items: serviceWorkflows } = useAppSelector( (state) => state.serviceWorkflows, ); diff --git a/src/modules/user/reducers.js b/src/modules/user/reducers.js index 04fa2e2e5..ac26049ae 100644 --- a/src/modules/user/reducers.js +++ b/src/modules/user/reducers.js @@ -2,7 +2,7 @@ import { FETCHING_USER_DEPENDENT_DATA } from "./actions"; export const user = ( state = { - isFetchingUserDependentData: false, + isFetchingDependentData: false, }, action, ) => { diff --git a/src/modules/wes/hooks.js b/src/modules/wes/hooks.ts similarity index 73% rename from src/modules/wes/hooks.js rename to src/modules/wes/hooks.ts index 9e9c2073a..d17bd1660 100644 --- a/src/modules/wes/hooks.js +++ b/src/modules/wes/hooks.ts @@ -1,14 +1,15 @@ import { useEffect } from "react"; -import { useSelector } from "react-redux"; import { RESOURCE_EVERYTHING, viewRuns } from "bento-auth-js"; import { useHasResourcePermissionWrapper } from "@/hooks"; import { useService } from "@/modules/services/hooks"; -import { fetchRuns } from "@/modules/wes/actions"; -import { useAppDispatch } from "@/store"; +import { useAppDispatch, useAppSelector } from "@/store"; -export const useRuns = () => { +import { fetchRuns } from "./actions"; +import type { WorkflowRunsState } from "./types"; + +export const useRuns = (): WorkflowRunsState => { const dispatch = useAppDispatch(); const wes = useService("wes"); // TODO: associate this with the network action somehow @@ -21,5 +22,5 @@ export const useRuns = () => { } }, [dispatch, wes, hasPermission]); - return useSelector((state) => state.runs); + return useAppSelector((state) => state.runs); }; diff --git a/src/modules/wes/types.ts b/src/modules/wes/types.ts index 65f7193b6..8c6bd8d69 100644 --- a/src/modules/wes/types.ts +++ b/src/modules/wes/types.ts @@ -91,3 +91,15 @@ export type WorkflowRunStreamState = { isFetching: boolean; data: string | null; }; + +export type WorkflowRunsState = { + isFetching: boolean; + hasAttempted: boolean; + isSubmittingRun: boolean; + items: WorkflowRunWithNestedDetailsState[]; + itemsByID: Record; + streamsByID: { + stdout?: WorkflowRunStreamState; + stderr?: WorkflowRunStreamState; + }; +};