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/modules/datasets/actions.js b/src/modules/datasets/actions.js
index 58b8e9eae..39cd3fc52 100644
--- a/src/modules/datasets/actions.js
+++ b/src/modules/datasets/actions.js
@@ -2,20 +2,21 @@ import { beginFlow, createFlowActionTypes, createNetworkActionTypes, endFlow, ne
import { getDataServices } from "../services/utils";
export const FETCHING_DATASETS_DATA_TYPES = createFlowActionTypes("FETCHING_DATASETS_DATA_TYPES");
-export const FETCH_DATASET_DATA_TYPES_SUMMARY = createNetworkActionTypes("FETCH_DATASET_DATA_TYPES_SUMMARY");
+export const FETCH_DATASET_DATA_TYPES = createNetworkActionTypes("FETCH_DATASET_DATA_TYPES");
-export const FETCH_DATASET_SUMMARY = createNetworkActionTypes("FETCH_DATASET_SUMMARY");
-export const FETCHING_ALL_DATASET_SUMMARIES = createFlowActionTypes("FETCHING_ALL_DATASET_SUMMARIES");
+export const FETCH_SERVICE_DATASET_SUMMARY = createNetworkActionTypes("FETCH_SERVICE_DATASET_SUMMARY");
+export const FETCHING_DATASET_SUMMARIES = createFlowActionTypes("FETCHING_DATASET_SUMMARIES");
+export const INVALIDATE_DATASET_SUMMARIES = "INVALIDATE_DATASET_SUMMARIES";
export const FETCH_DATASET_RESOURCES = createNetworkActionTypes("FETCH_DATASET_RESOURCES");
const fetchDatasetDataTypesSummary = networkAction((serviceInfo, datasetID) => ({
- types: FETCH_DATASET_DATA_TYPES_SUMMARY,
+ types: FETCH_DATASET_DATA_TYPES,
params: { serviceInfo, datasetID },
url: `${serviceInfo.url}/datasets/${datasetID}/data-types`,
}));
-export const fetchDatasetDataTypesSummariesIfPossible = (datasetID) => async (dispatch, getState) => {
+export const fetchDatasetDataTypesIfPossible = (datasetID) => async (dispatch, getState) => {
if (getState().datasetDataTypes.itemsByID?.[datasetID]?.isFetching) return;
await Promise.all(
getDataServices(getState()).map((serviceInfo) => dispatch(fetchDatasetDataTypesSummary(serviceInfo, datasetID))),
@@ -26,27 +27,30 @@ export const fetchDatasetsDataTypes = () => async (dispatch, getState) => {
dispatch(beginFlow(FETCHING_DATASETS_DATA_TYPES));
await Promise.all(
Object.keys(getState().projects.datasetsByID).map((datasetID) =>
- dispatch(fetchDatasetDataTypesSummariesIfPossible(datasetID)),
+ dispatch(fetchDatasetDataTypesIfPossible(datasetID)),
),
);
dispatch(endFlow(FETCHING_DATASETS_DATA_TYPES));
};
-const fetchDatasetSummary = networkAction((serviceInfo, datasetID) => ({
- types: FETCH_DATASET_SUMMARY,
+const fetchServiceDatasetSummary = networkAction((serviceInfo, datasetID) => ({
+ types: FETCH_SERVICE_DATASET_SUMMARY,
params: { serviceInfo, datasetID },
url: `${serviceInfo.url}/datasets/${datasetID}/summary`,
}));
-export const fetchDatasetSummariesIfPossible = (datasetID) => async (dispatch, getState) => {
- if (getState().datasetSummaries.isFetchingAll) return;
- dispatch(beginFlow(FETCHING_ALL_DATASET_SUMMARIES));
+export const fetchDatasetSummariesIfNeeded = (datasetID) => async (dispatch, getState) => {
+ const existingSummaryState = getState().datasetSummaries.itemsByID[datasetID] ?? {};
+ if (existingSummaryState.isFetching || (!existingSummaryState.isInvalid && existingSummaryState.hasAttempted)) return;
+ dispatch(beginFlow(FETCHING_DATASET_SUMMARIES, { datasetID }));
await Promise.all(
- getDataServices(getState()).map((serviceInfo) => dispatch(fetchDatasetSummary(serviceInfo, datasetID))),
+ getDataServices(getState()).map((serviceInfo) => dispatch(fetchServiceDatasetSummary(serviceInfo, datasetID))),
);
- dispatch(endFlow(FETCHING_ALL_DATASET_SUMMARIES));
+ dispatch(endFlow(FETCHING_DATASET_SUMMARIES, { datasetID }));
};
+export const invalidateDatasetSummaries = (datasetID) => ({ type: INVALIDATE_DATASET_SUMMARIES, datasetID });
+
const fetchDatasetResources = networkAction((datasetID) => (_dispatch, getState) => ({
types: FETCH_DATASET_RESOURCES,
params: { datasetID },
diff --git a/src/modules/datasets/hooks.js b/src/modules/datasets/hooks.js
index 0cefee788..6fded291c 100644
--- a/src/modules/datasets/hooks.js
+++ b/src/modules/datasets/hooks.js
@@ -1,7 +1,11 @@
import { useEffect, useMemo } from "react";
import { useProjects } from "@/modules/metadata/hooks";
import { useAppDispatch, useAppSelector } from "@/store";
-import { fetchDatasetDataTypesSummariesIfPossible, fetchDatasetsDataTypes } from "@/modules/datasets/actions";
+import {
+ fetchDatasetDataTypesIfPossible,
+ fetchDatasetsDataTypes,
+ fetchDatasetSummariesIfNeeded,
+} from "@/modules/datasets/actions";
export const useDatasetDataTypes = () => {
/**
@@ -23,17 +27,30 @@ export const useDatasetDataTypesByID = (datasetId) => {
* Fetches the data types ONLY for the given dataset.
* Returns the store's value for the given dataset ID.
*/
+
const dispatch = useAppDispatch();
useEffect(() => {
- dispatch(fetchDatasetDataTypesSummariesIfPossible(datasetId));
+ dispatch(fetchDatasetDataTypesIfPossible(datasetId)).catch(console.error);
}, [dispatch, datasetId]);
const dataTypes = useAppSelector((state) => state.datasetDataTypes.itemsByID[datasetId]);
return useMemo(() => {
return {
- dataTypesByID: dataTypes?.itemsByID,
+ dataTypesByID: dataTypes?.itemsByID ?? {},
isFetchingDataTypes: dataTypes?.isFetching ?? true,
hasAttemptedDataTypes: dataTypes?.hasAttempted ?? false,
};
}, [dataTypes]);
};
+
+export const useDatasetSummariesByID = (datasetId) => {
+ /**
+ * Fetches the data type summaries ONLY for the given dataset.
+ * Returns the store's value for the given dataset ID.
+ */
+ const dispatch = useAppDispatch();
+ useEffect(() => {
+ dispatch(fetchDatasetSummariesIfNeeded(datasetId)).catch(console.error);
+ }, [dispatch, datasetId]);
+ return useAppSelector((state) => state.datasetSummaries.itemsByID[datasetId]);
+};
diff --git a/src/modules/datasets/reducers.js b/src/modules/datasets/reducers.js
index 5c241d047..68d06ee60 100644
--- a/src/modules/datasets/reducers.js
+++ b/src/modules/datasets/reducers.js
@@ -1,9 +1,10 @@
import {
- FETCHING_DATASETS_DATA_TYPES,
- FETCH_DATASET_DATA_TYPES_SUMMARY,
- FETCH_DATASET_SUMMARY,
- FETCHING_ALL_DATASET_SUMMARIES,
+ FETCH_DATASET_DATA_TYPES,
FETCH_DATASET_RESOURCES,
+ FETCH_SERVICE_DATASET_SUMMARY,
+ FETCHING_DATASET_SUMMARIES,
+ FETCHING_DATASETS_DATA_TYPES,
+ INVALIDATE_DATASET_SUMMARIES,
} from "./actions";
export const datasetDataTypes = (
@@ -18,7 +19,7 @@ export const datasetDataTypes = (
return { ...state, isFetchingAll: true };
case FETCHING_DATASETS_DATA_TYPES.END:
return { ...state, isFetchingAll: false };
- case FETCH_DATASET_DATA_TYPES_SUMMARY.REQUEST: {
+ case FETCH_DATASET_DATA_TYPES.REQUEST: {
const { datasetID } = action;
return {
...state,
@@ -32,7 +33,7 @@ export const datasetDataTypes = (
},
};
}
- case FETCH_DATASET_DATA_TYPES_SUMMARY.RECEIVE: {
+ case FETCH_DATASET_DATA_TYPES.RECEIVE: {
const { datasetID } = action;
const itemsByID = Object.fromEntries(action.data.map((d) => [d.id, d]));
return {
@@ -48,8 +49,8 @@ export const datasetDataTypes = (
},
};
}
- case FETCH_DATASET_DATA_TYPES_SUMMARY.FINISH:
- case FETCH_DATASET_DATA_TYPES_SUMMARY.ERROR: {
+ case FETCH_DATASET_DATA_TYPES.FINISH:
+ case FETCH_DATASET_DATA_TYPES.ERROR: {
const { datasetID } = action;
return {
...state,
@@ -78,7 +79,7 @@ const datasetItemSet = (oldState, datasetID, key, value) => {
...value,
}
: value;
- const newState = {
+ return {
...oldState,
itemsByID: {
...oldState.itemsByID,
@@ -88,28 +89,36 @@ const datasetItemSet = (oldState, datasetID, key, value) => {
},
},
};
- return newState;
};
export const datasetSummaries = (
state = {
- isFetchingAll: false,
itemsByID: {},
},
action,
) => {
+ // This reducer is a bit funky, since it is combining data from multiple services.
switch (action.type) {
- case FETCH_DATASET_SUMMARY.REQUEST:
- return datasetItemSet(state, action.datasetID, "isFetching", true);
- case FETCH_DATASET_SUMMARY.RECEIVE:
+ case FETCH_SERVICE_DATASET_SUMMARY.RECEIVE:
return datasetItemSet(state, action.datasetID, "data", action.data);
- case FETCH_DATASET_SUMMARY.FINISH:
- return datasetItemSet(state, action.datasetID, "isFetching", false);
- case FETCHING_ALL_DATASET_SUMMARIES.BEGIN:
- return { ...state, isFetchingAll: true };
- case FETCHING_ALL_DATASET_SUMMARIES.END:
- case FETCHING_ALL_DATASET_SUMMARIES.TERMINATE:
- return { ...state, isFetchingAll: false };
+ case FETCHING_DATASET_SUMMARIES.BEGIN:
+ return datasetItemSet(state, action.datasetID, "isFetching", true);
+ case FETCHING_DATASET_SUMMARIES.END:
+ case FETCHING_DATASET_SUMMARIES.TERMINATE:
+ return {
+ ...state,
+ itemsByID: {
+ ...state.itemsByID,
+ [action.datasetID]: {
+ ...(state.itemsByID[action.datasetID] ?? {}),
+ isFetching: false,
+ isInvalid: false,
+ hasAttempted: true,
+ },
+ },
+ };
+ case INVALIDATE_DATASET_SUMMARIES:
+ return datasetItemSet(state, action.datasetID, "isInvalid", true);
default:
return state;
}
diff --git a/src/utils/actions.js b/src/utils/actions.js
index 93e94ba3a..dfc13fd69 100644
--- a/src/utils/actions.js
+++ b/src/utils/actions.js
@@ -157,6 +157,6 @@ const formatErrorMessage = (errorMessageIntro, errorDetail) => {
return errorMessageIntro ? errorMessageIntro + (errorDetail ? `: ${errorDetail}` : "") : errorDetail;
};
-export const beginFlow = (types) => (dispatch) => dispatch({ type: types.BEGIN });
-export const endFlow = (types) => (dispatch) => dispatch({ type: types.END });
-export const terminateFlow = (types) => (dispatch) => dispatch({ type: types.TERMINATE });
+export const beginFlow = (types, params) => (dispatch) => dispatch({ type: types.BEGIN, ...(params ?? {}) });
+export const endFlow = (types, params) => (dispatch) => dispatch({ type: types.END, ...(params ?? {}) });
+export const terminateFlow = (types, params) => (dispatch) => dispatch({ type: types.TERMINATE, ...(params ?? {}) });