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/Dataset.js b/src/components/datasets/Dataset.js
index ba3bbf89d..b098fae4a 100644
--- a/src/components/datasets/Dataset.js
+++ b/src/components/datasets/Dataset.js
@@ -13,7 +13,7 @@ import {
deleteDatasetLinkedFieldSetIfPossible,
} from "@/modules/metadata/actions";
-import { fetchDatasetDataTypesSummariesIfPossible, fetchDatasetSummariesIfPossible } from "@/modules/datasets/actions";
+import { fetchDatasetDataTypesIfPossible, fetchDatasetSummariesIfNeeded } from "@/modules/datasets/actions";
import { INITIAL_DATA_USE_VALUE } from "@/duo";
import { simpleDeepCopy, nop } from "@/utils/misc";
@@ -335,8 +335,8 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
deleteProjectDataset: (dataset) => dispatch(deleteProjectDatasetIfPossible(ownProps.project, dataset)),
deleteLinkedFieldSet: (dataset, linkedFieldSet, linkedFieldSetIndex) =>
dispatch(deleteDatasetLinkedFieldSetIfPossible(dataset, linkedFieldSet, linkedFieldSetIndex)),
- fetchDatasetSummary: (datasetId) => dispatch(fetchDatasetSummariesIfPossible(datasetId)),
- fetchDatasetDataTypesSummary: (datasetId) => dispatch(fetchDatasetDataTypesSummariesIfPossible(datasetId)),
+ fetchDatasetSummary: (datasetId) => dispatch(fetchDatasetSummariesIfNeeded(datasetId)),
+ fetchDatasetDataTypesSummary: (datasetId) => dispatch(fetchDatasetDataTypesIfPossible(datasetId)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Dataset);
diff --git a/src/components/datasets/DatasetDataTypes.js b/src/components/datasets/DatasetDataTypes.js
index 690239591..73615e5ca 100644
--- a/src/components/datasets/DatasetDataTypes.js
+++ b/src/components/datasets/DatasetDataTypes.js
@@ -1,14 +1,15 @@
import { memo, useCallback, useMemo, useState } from "react";
-import { useSelector, useDispatch } from "react-redux";
import PropTypes from "prop-types";
import { Button, Col, Dropdown, Row, Table, Typography } from "antd";
import { DeleteOutlined, DownOutlined, ImportOutlined } from "@ant-design/icons";
import { datasetPropTypesShape, projectPropTypesShape } from "@/propTypes";
-import { fetchDatasetDataTypesSummariesIfPossible } from "@/modules/datasets/actions";
+import { fetchDatasetDataTypesIfPossible, invalidateDatasetSummaries } from "@/modules/datasets/actions";
+import { useDatasetDataTypesByID, useDatasetSummariesByID } from "@/modules/datasets/hooks";
import { clearDatasetDataType } from "@/modules/metadata/actions";
import { useWorkflows } from "@/modules/services/hooks";
+import { useAppDispatch } from "@/store";
import { useStartIngestionFlow } from "../manager/workflowCommon";
import genericConfirm from "../ConfirmationModal";
@@ -17,20 +18,19 @@ import DataTypeSummaryModal from "./datatype/DataTypeSummaryModal";
const NA_TEXT = N/A;
const DatasetDataTypes = memo(({ isPrivate, project, dataset }) => {
- const dispatch = useDispatch();
- const datasetDataTypes = useSelector((state) => state.datasetDataTypes.itemsByID[dataset.identifier]);
- const datasetDataTypeValues = useMemo(() => Object.values(datasetDataTypes?.itemsByID ?? {}), [datasetDataTypes]);
- const datasetSummaries = useSelector((state) => state.datasetSummaries.itemsByID[dataset.identifier]);
+ const dispatch = useAppDispatch();
+ const datasetDataTypes = useDatasetDataTypesByID(dataset.identifier);
+ const datasetDataTypeValues = useMemo(() => Object.values(datasetDataTypes.dataTypesByID), [datasetDataTypes]);
+ const datasetSummaries = useDatasetSummariesByID(dataset.identifier);
- const isFetchingDataset = datasetDataTypes?.isFetching ?? false;
+ const isFetchingDataset = datasetDataTypes.isFetching ?? false;
const { workflowsByType } = useWorkflows();
const ingestionWorkflows = workflowsByType.ingestion.items;
- const [datatypeSummaryVisible, setDatatypeSummaryVisible] = useState(false);
const [selectedDataType, setSelectedDataType] = useState(null);
- const selectedSummary = datasetSummaries?.data?.[selectedDataType?.id] ?? {};
+ const selectedDataTypeSummary = datasetSummaries?.data?.[selectedDataType?.id] ?? {};
const handleClearDataType = useCallback(
(dataType) => {
@@ -41,7 +41,8 @@ const DatasetDataTypes = memo(({ isPrivate, project, dataset }) => {
"will be deleted permanently, and will no longer be available for exploration.",
onOk: async () => {
await dispatch(clearDatasetDataType(dataset.identifier, dataType.id));
- await dispatch(fetchDatasetDataTypesSummariesIfPossible(dataset.identifier));
+ await dispatch(fetchDatasetDataTypesIfPossible(dataset.identifier));
+ dispatch(invalidateDatasetSummaries(dataset.identifier));
},
});
},
@@ -50,7 +51,6 @@ const DatasetDataTypes = memo(({ isPrivate, project, dataset }) => {
const showDataTypeSummary = useCallback((dataType) => {
setSelectedDataType(dataType);
- setDatatypeSummaryVisible(true);
}, []);
const startIngestionFlow = useStartIngestionFlow();
@@ -124,15 +124,16 @@ const DatasetDataTypes = memo(({ isPrivate, project, dataset }) => {
[isPrivate, project, dataset, handleClearDataType, ingestionWorkflows, startIngestionFlow, showDataTypeSummary],
);
- const onDataTypeSummaryModalCancel = useCallback(() => setDatatypeSummaryVisible(false), []);
+ const onDataTypeSummaryModalCancel = useCallback(() => setSelectedDataType(null), []);
return (
<>
diff --git a/src/components/datasets/datatype/DataTypeSummaryModal.js b/src/components/datasets/datatype/DataTypeSummaryModal.js
index b42dd6acc..334886b69 100644
--- a/src/components/datasets/datatype/DataTypeSummaryModal.js
+++ b/src/components/datasets/datatype/DataTypeSummaryModal.js
@@ -1,7 +1,6 @@
-import { useSelector } from "react-redux";
import PropTypes from "prop-types";
-import { Modal, Skeleton } from "antd";
+import { Alert, Modal, Skeleton } from "antd";
import { summaryPropTypesShape } from "@/propTypes";
@@ -9,9 +8,7 @@ import GenericSummary from "./GenericSummary";
import PhenopacketSummary from "./PhenopacketSummary";
import VariantSummary from "./VariantSummary";
-const DataTypeSummaryModal = ({ dataType, summary, onCancel, open }) => {
- const isFetchingSummaries = useSelector((state) => state.datasetDataTypes.isFetchingAll);
-
+const DataTypeSummaryModal = ({ dataType, summary, onCancel, open, isFetching }) => {
if (!dataType) {
return <>>;
}
@@ -38,7 +35,9 @@ const DataTypeSummaryModal = ({ dataType, summary, onCancel, open }) => {
width={960}
footer={null}
>
- {!summaryData || isFetchingSummaries ? : }
+
+ {!summaryData || isFetching ? : }
+
);
};
@@ -48,6 +47,7 @@ DataTypeSummaryModal.propTypes = {
summary: summaryPropTypesShape,
onCancel: PropTypes.func,
open: PropTypes.bool,
+ isFetching: PropTypes.bool,
};
export default DataTypeSummaryModal;
diff --git a/src/components/datasets/datatype/VariantSummary.js b/src/components/datasets/datatype/VariantSummary.js
index e986a6505..c4855c23f 100644
--- a/src/components/datasets/datatype/VariantSummary.js
+++ b/src/components/datasets/datatype/VariantSummary.js
@@ -1,21 +1,16 @@
import { Col, Row, Statistic } from "antd";
-import { FileOutlined } from "@ant-design/icons";
+import { EM_DASH } from "@/constants";
import { summaryPropTypesShape } from "@/propTypes";
const VariantSummary = ({ summary }) => (
-
+
-
-
+
+
- {summary.data_type_specific?.vcf_files !== undefined ? (
-
- } value={summary.data_type_specific.vcf_files} />
-
- ) : null}
);
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 (