From 627959fab8f04833a6e3eac6f55e493ac858bab3 Mon Sep 17 00:00:00 2001 From: Artur Rybalskyy Date: Mon, 8 Jun 2026 14:49:07 +0300 Subject: [PATCH] [FIX] Improve loader UX and abort race condition handling --- src/api/monitoringApplications-api.js | 18 +++++--- .../AddToFeatureVectorPage.jsx | 1 + .../AddToFeatureVectorView.jsx | 2 +- src/components/Alerts/Alerts.jsx | 2 +- .../ApplicationMetrics/ApplicationMetrics.jsx | 5 ++- .../ApplicationMetrics.scss | 1 + src/components/Artifacts/Artifacts.jsx | 15 ++++--- src/components/Artifacts/ArtifactsTable.jsx | 6 ++- src/components/Artifacts/ArtifactsView.jsx | 1 - .../ArtifactsPreview/ArtifactsPreview.jsx | 2 +- .../ConsumerGroup/ConsumerGroup.jsx | 2 +- .../ConsumerGroups/ConsumerGroups.jsx | 2 +- .../FeatureSetsPanel/FeatureSetsPanelView.jsx | 2 +- .../FeatureStore/FeatureSets/FeatureSets.jsx | 2 + .../FeatureSets/FeatureSetsView.jsx | 6 ++- src/components/FeatureStore/FeatureStore.jsx | 5 +-- .../FeatureVectors/FeatureVectors.jsx | 2 + .../FeatureVectors/FeatureVectorsView.jsx | 5 ++- .../FeatureStore/Features/Features.jsx | 2 + .../FeatureStore/Features/FeaturesView.jsx | 5 ++- src/components/FunctionsPage/Functions.jsx | 8 +++- .../FunctionsPage/FunctionsView.jsx | 4 +- .../FunctionsPageOld/FunctionsOld.jsx | 8 +++- .../FunctionsPageOld/FunctionsViewOld.jsx | 4 +- .../FunctionsPanel/FunctionsPanelView.jsx | 2 +- src/components/JobWizard/JobWizard.jsx | 2 +- src/components/Jobs/Jobs.jsx | 2 +- .../ModelEndpoints/ModelEndpoints.jsx | 2 + .../ModelEndpoints/ModelEndpointsTable.jsx | 9 ++-- src/components/ModelsPage/ModelsPage.jsx | 1 - .../RealTimePipelines/RealTimePipelines.jsx | 7 +-- .../RealTimePipelines/realTimePipelines.scss | 1 + .../MonitoringApplicationsPage.jsx | 44 ++++++++++++++++--- src/components/Project/ProjectMonitor.jsx | 3 ++ src/components/Project/ProjectMonitorView.jsx | 2 +- .../ProjectOverview/ProjectOverview.jsx | 2 +- .../ProjectsJobsMonitoring.jsx | 2 +- .../CreateProjectDialog.jsx | 2 +- src/components/ProjectsPage/Projects.jsx | 3 +- src/components/ProjectsPage/ProjectsView.jsx | 2 +- src/elements/AlertsTable/AlertsTable.jsx | 4 +- .../FeatureStoreTableRow.jsx | 2 +- src/elements/JobsTable/JobsTable.jsx | 2 +- .../ProjectSettingsGeneral.jsx | 4 +- .../ProjectSettingsMembers.jsx | 2 +- .../ProjectSettingsSecrets.jsx | 2 +- .../ScheduledJobsTable/ScheduledJobsTable.jsx | 2 +- .../WorkflowsTable/WorkflowsTable.jsx | 2 +- src/hooks/useJobsPageData.js | 7 +++ src/hooks/useRefreshAlerts.hook.js | 4 +- src/httpClient.js | 4 ++ .../EndpointDetailsDialog.jsx | 2 +- .../ApplicationsPage/ApplicationsPage.jsx | 4 +- .../shared/DetailsDataTab/DetailsDataTab.jsx | 2 +- src/reducers/alertsReducer.js | 10 ++++- src/reducers/artifactsReducer.js | 19 +++++--- src/reducers/featureStoreReducer.js | 6 ++- src/reducers/functionReducer.js | 2 + src/reducers/jobReducer.js | 21 +++++++-- src/reducers/monitoringApplicationsReducer.js | 40 +++++++++++------ src/reducers/nuclioReducer.js | 12 ++--- src/reducers/workflowReducer.js | 2 + src/utils/getArtifactPreview.jsx | 5 +-- src/utils/isRequestAborted.js | 23 ++++++++++ src/utils/largeResponseCatchHandler.js | 12 ++--- 65 files changed, 277 insertions(+), 114 deletions(-) create mode 100644 src/utils/isRequestAborted.js diff --git a/src/api/monitoringApplications-api.js b/src/api/monitoringApplications-api.js index 74c34aa548..f911b4bcfc 100644 --- a/src/api/monitoringApplications-api.js +++ b/src/api/monitoringApplications-api.js @@ -20,19 +20,23 @@ such restriction. import { mainHttpClient } from '../httpClient' const monitoringApplications = { - getMEPWithDetections: (project, params) => + getMEPWithDetections: (project, params, signal) => mainHttpClient.get(`projects/${project}/model-monitoring/drift-over-time`, { - params + params, + signal }), - getMonitoringApplication: (project, functionName, params) => + getMonitoringApplication: (project, functionName, params, signal) => mainHttpClient.get(`projects/${project}/model-monitoring/function-summaries/${functionName}`, { - params + params, + signal }), - getMonitoringApplications: (project, params) => + getMonitoringApplications: (project, params, signal) => mainHttpClient.get(`projects/${project}/model-monitoring/function-summaries`, { - params + params, + signal }), - getMonitoringApplicationsSummary: project => mainHttpClient.get(`project-summaries/${project}`) + getMonitoringApplicationsSummary: (project, signal) => + mainHttpClient.get(`project-summaries/${project}`, { signal }) } export default monitoringApplications diff --git a/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.jsx b/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.jsx index 87a33c71cc..e1975f5039 100644 --- a/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.jsx +++ b/src/components/AddToFeatureVectorPage/AddToFeatureVectorPage.jsx @@ -179,6 +179,7 @@ const AddToFeatureVectorPage = () => { const fetchData = useCallback( async filters => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const cancelRequestTimeout = setTimeout(() => { diff --git a/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.jsx b/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.jsx index d9f0b89ecf..ad150741b1 100644 --- a/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.jsx +++ b/src/components/AddToFeatureVectorPage/AddToFeatureVectorView.jsx @@ -76,7 +76,7 @@ const AddToFeatureVectorView = React.forwardRef( - {(featureStore.loading || featureStore.features.loading) && } + {(featureStore.loading || featureStore.features.loading) && }
{featureStore.loading || featureStore.features.loading ? null : content.length === 0 ? ( {
- {alertsStore.loading && } + {alertsStore.loading && } {alertsStore.loading ? null : ( <> { const dispatch = useDispatch() const navigate = useNavigate() const params = useParams() - const abortControllerRef = useRef() + const abortControllerRef = useRef(new AbortController()) const filteredEndpoints = useMemo(() => { return modelEndpoints.filter(modelEndpoint => { @@ -121,6 +121,7 @@ const ApplicationMetrics = () => { ) const fetchModelEndpointsData = useCallback(() => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() dispatch( @@ -271,7 +272,7 @@ const ApplicationMetrics = () => {
{(artifactsStore.modelEndpoints.loading || applicationsStore.loading || - detailsStore.loadingCounter > 0) && } + detailsStore.loadingCounter > 0) && } {artifactsStore.modelEndpoints.loading || applicationsStore.loading ? null : modelEndpoints.length === 0 ? ( diff --git a/src/components/ApplicationMetrics/ApplicationMetrics.scss b/src/components/ApplicationMetrics/ApplicationMetrics.scss index 9d2340155b..14044073c7 100644 --- a/src/components/ApplicationMetrics/ApplicationMetrics.scss +++ b/src/components/ApplicationMetrics/ApplicationMetrics.scss @@ -23,6 +23,7 @@ $searchHeight: 75px; } .list-view { + position: relative; display: flex; gap: 20px; height: calc(100% - 60px); diff --git a/src/components/Artifacts/Artifacts.jsx b/src/components/Artifacts/Artifacts.jsx index 130da8b174..019cbcff1e 100644 --- a/src/components/Artifacts/Artifacts.jsx +++ b/src/components/Artifacts/Artifacts.jsx @@ -48,6 +48,7 @@ import { getFeatureVectorData } from '../ModelsPage/Models/models.util' import { getFilterTagOptions, setFilters } from '../../reducers/filtersReducer' import { getFiltersConfig } from './artifacts.util' import { getSavedSearchParams, transformSearchParams } from 'igz-controls/utils/filter.util' +import { isRequestAborted } from '../../utils/isRequestAborted' import { openPopUp, getViewMode } from 'igz-controls/utils/common.util' import { setNotification } from 'igz-controls/reducers/notificationReducer' import { toggleYaml } from '../../reducers/appReducer' @@ -136,6 +137,7 @@ const Artifacts = ({ const fetchData = useCallback( async filters => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const requestParams = { @@ -201,11 +203,13 @@ const Artifacts = ({ return response }) - .catch(() => { - if (isAllVersions) { - setArtifactVersions([]) - } else { - setArtifacts([]) + .catch(error => { + if (!isRequestAborted(error?.message)) { + if (isAllVersions) { + setArtifactVersions([]) + } else { + setArtifacts([]) + } } }) }, @@ -213,6 +217,7 @@ const Artifacts = ({ ) const fetchTags = useCallback(() => { + tagAbortControllerRef.current.abort(REQUEST_CANCELED) tagAbortControllerRef.current = new AbortController() return dispatch( diff --git a/src/components/Artifacts/ArtifactsTable.jsx b/src/components/Artifacts/ArtifactsTable.jsx index d536e922bc..d7b1e22973 100644 --- a/src/components/Artifacts/ArtifactsTable.jsx +++ b/src/components/Artifacts/ArtifactsTable.jsx @@ -104,7 +104,9 @@ let ArtifactsTable = ({
{renderPageTabs && renderHistoryBackLink()} - {artifactsStore.loading ? null : tableContent.length === 0 && isEmpty(selectedArtifact) ? ( + {artifactsStore.loading ? ( + + ) : tableContent.length === 0 && isEmpty(selectedArtifact) ? ( ) : ( <> - {storeArtifactTypeLoading && } + {storeArtifactTypeLoading && }
- {artifactsStore.loading && } { return !noData && preview.length === 0 ? (
- +
) : noData ? ( diff --git a/src/components/ConsumerGroup/ConsumerGroup.jsx b/src/components/ConsumerGroup/ConsumerGroup.jsx index b9352dc127..3e471168ad 100644 --- a/src/components/ConsumerGroup/ConsumerGroup.jsx +++ b/src/components/ConsumerGroup/ConsumerGroup.jsx @@ -174,7 +174,7 @@ const ConsumerGroup = () => { )} /> )} - {(nuclioStore.v3ioStreams.loading || nuclioStore.v3ioStreamShardLags.loading) && } + {(nuclioStore.v3ioStreams.loading || nuclioStore.v3ioStreamShardLags.loading) && } ) } diff --git a/src/components/ConsumerGroups/ConsumerGroups.jsx b/src/components/ConsumerGroups/ConsumerGroups.jsx index 099c79b4a9..561c9761ec 100644 --- a/src/components/ConsumerGroups/ConsumerGroups.jsx +++ b/src/components/ConsumerGroups/ConsumerGroups.jsx @@ -113,7 +113,7 @@ const ConsumerGroups = () => { )} /> )} - {nuclioStore.v3ioStreams.loading && } + {nuclioStore.v3ioStreams.loading && } ) } diff --git a/src/components/FeatureSetsPanel/FeatureSetsPanelView.jsx b/src/components/FeatureSetsPanel/FeatureSetsPanelView.jsx index ef542f1ca9..4c69aba53f 100644 --- a/src/components/FeatureSetsPanel/FeatureSetsPanelView.jsx +++ b/src/components/FeatureSetsPanel/FeatureSetsPanelView.jsx @@ -60,7 +60,7 @@ const FeatureSetsPanelView = ({ return (
- {loading && } + {loading && } {confirmDialog && ( setConfirmDialog(null)} diff --git a/src/components/FeatureStore/FeatureSets/FeatureSets.jsx b/src/components/FeatureStore/FeatureSets/FeatureSets.jsx index aad67d730d..49d1246921 100644 --- a/src/components/FeatureStore/FeatureSets/FeatureSets.jsx +++ b/src/components/FeatureStore/FeatureSets/FeatureSets.jsx @@ -117,6 +117,7 @@ const FeatureSets = () => { const fetchData = useCallback( filters => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const config = { @@ -145,6 +146,7 @@ const FeatureSets = () => { ) const fetchTags = useCallback(() => { + tagAbortControllerRef.current.abort(REQUEST_CANCELED) tagAbortControllerRef.current = new AbortController() return dispatch( diff --git a/src/components/FeatureStore/FeatureSets/FeatureSetsView.jsx b/src/components/FeatureStore/FeatureSets/FeatureSetsView.jsx index 8609613980..9a2d9493d7 100644 --- a/src/components/FeatureStore/FeatureSets/FeatureSetsView.jsx +++ b/src/components/FeatureStore/FeatureSets/FeatureSetsView.jsx @@ -92,7 +92,9 @@ const FeatureSetsView = React.forwardRef(
- {featureStore.loading ? null : featureSets.length === 0 ? ( + {featureStore.loading ? ( + + ) : featureSets.length === 0 ? ( ) : ( <> - {(selectedRowData.loading || featureStore.featureSets.featureSetLoading) && } + {(selectedRowData.loading || featureStore.featureSets.featureSetLoading) && }
{ > - {(featureStore.loading || - featureStore.entities.loading || - featureStore.features.loading) && } diff --git a/src/components/FeatureStore/FeatureVectors/FeatureVectors.jsx b/src/components/FeatureStore/FeatureVectors/FeatureVectors.jsx index 3ce68e58d0..c284ca5077 100644 --- a/src/components/FeatureStore/FeatureVectors/FeatureVectors.jsx +++ b/src/components/FeatureStore/FeatureVectors/FeatureVectors.jsx @@ -118,6 +118,7 @@ const FeatureVectors = () => { const fetchData = useCallback( filters => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const config = { @@ -143,6 +144,7 @@ const FeatureVectors = () => { ) const fetchTags = useCallback(() => { + tagAbortControllerRef.current.abort(REQUEST_CANCELED) tagAbortControllerRef.current = new AbortController() return dispatch( diff --git a/src/components/FeatureStore/FeatureVectors/FeatureVectorsView.jsx b/src/components/FeatureStore/FeatureVectors/FeatureVectorsView.jsx index 127a5cbf8e..f1df07957e 100644 --- a/src/components/FeatureStore/FeatureVectors/FeatureVectorsView.jsx +++ b/src/components/FeatureStore/FeatureVectors/FeatureVectorsView.jsx @@ -18,6 +18,7 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ import React from 'react' +import { Loader } from 'igz-controls/components' import PropTypes from 'prop-types' import ActionBar from '../../ActionBar/ActionBar' @@ -86,7 +87,9 @@ const FeatureVectorsView = React.forwardRef( - {featureStore.loading ? null : featureVectors.length === 0 ? ( + {featureStore.loading ? ( + + ) : featureVectors.length === 0 ? ( { const fetchData = useCallback( filters => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const cancelRequestTimeout = setTimeout(() => { @@ -163,6 +164,7 @@ const Features = () => { ) const fetchTags = useCallback(() => { + tagAbortControllerRef.current.abort(REQUEST_CANCELED) tagAbortControllerRef.current = new AbortController() return dispatch( diff --git a/src/components/FeatureStore/Features/FeaturesView.jsx b/src/components/FeatureStore/Features/FeaturesView.jsx index f1df3f8c2e..1aaaa71f80 100644 --- a/src/components/FeatureStore/Features/FeaturesView.jsx +++ b/src/components/FeatureStore/Features/FeaturesView.jsx @@ -26,6 +26,7 @@ import FeatureStorePageTabs from '../FeatureStorePageTabs/FeatureStorePageTabs' import FeatureStoreTableRow from '../../../elements/FeatureStoreTableRow/FeatureStoreTableRow' import NoData from '../../../common/NoData/NoData' import Table from '../../Table/Table' +import { Loader } from 'igz-controls/components' import { FEATURE_STORE_PAGE, FEATURES_TAB } from '../../../constants' import { PRIMARY_BUTTON } from 'igz-controls/constants' @@ -81,7 +82,9 @@ const FeaturesView = React.forwardRef( - {featureStore.features.loading || featureStore.entities.loading ? null : features.length === + {featureStore.features.loading || featureStore.entities.loading ? ( + + ) : features.length === 0 ? ( { const fetchData = useCallback( filters => { terminateDeleteTasksPolling() + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const requestParams = { format: 'minimal', @@ -237,8 +239,10 @@ const Functions = ({ isAllVersions = false }) => { resetFunctions([]) } }) - .catch(() => { - resetFunctions([]) + .catch(error => { + if (!isRequestAborted(error?.message)) { + resetFunctions([]) + } }) }, [ diff --git a/src/components/FunctionsPage/FunctionsView.jsx b/src/components/FunctionsPage/FunctionsView.jsx index f058f7abe1..40d4963e46 100644 --- a/src/components/FunctionsPage/FunctionsView.jsx +++ b/src/components/FunctionsPage/FunctionsView.jsx @@ -113,7 +113,7 @@ const FunctionsView = ({ {functionsStore.loading ? ( - + ) : tableContent.length === 0 && isEmpty(selectedFunction) ? ( ) : ( <> - {functionsStore.funcLoading && } + {functionsStore.funcLoading && }
{ const fetchData = useCallback( (filters, filtersAreHandled = false) => { terminateDeleteTasksPolling() + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() setFunctions([]) @@ -201,8 +203,10 @@ const Functions = () => { } } }) - .catch(() => { - setFunctions([]) + .catch(error => { + if (!isRequestAborted(error?.message)) { + setFunctions([]) + } }) }, [ diff --git a/src/components/FunctionsPageOld/FunctionsViewOld.jsx b/src/components/FunctionsPageOld/FunctionsViewOld.jsx index d29cb7f2ab..375c16415b 100644 --- a/src/components/FunctionsPageOld/FunctionsViewOld.jsx +++ b/src/components/FunctionsPageOld/FunctionsViewOld.jsx @@ -105,7 +105,7 @@ const FunctionsViewOld = ({ {functionsStore.loading ? ( - + ) : functions.length === 0 ? ( ) : ( <> - {functionsStore.funcLoading && } + {functionsStore.funcLoading && }
- {loading && } + {loading && }
} + projectIsLoading) && } ) }} diff --git a/src/components/Jobs/Jobs.jsx b/src/components/Jobs/Jobs.jsx index ee7fb31042..14f76eaa4d 100644 --- a/src/components/Jobs/Jobs.jsx +++ b/src/components/Jobs/Jobs.jsx @@ -312,7 +312,7 @@ const Jobs = () => { {(Boolean(jobsStore.jobLoadingCounter) || workflowsStore.activeWorkflow.loading || - functionsStore.funcLoading) && } + functionsStore.funcLoading) && }
)} diff --git a/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.jsx b/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.jsx index 92eece364b..afe1c3ea9a 100644 --- a/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.jsx +++ b/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.jsx @@ -25,6 +25,7 @@ import ModelEndpointsTable from './ModelEndpointsTable' import { fetchModelEndpoints } from '../../../reducers/artifactsReducer' import { filtersConfig } from './modelEndpoints.util' +import { REQUEST_CANCELED } from '../../../constants' import { useFiltersFromSearchParams } from '../../../hooks/useFiltersFromSearchParams.hook' const ModelEndpoints = () => { @@ -37,6 +38,7 @@ const ModelEndpoints = () => { const fetchEndpoints = useCallback( filters => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() return dispatch( diff --git a/src/components/ModelsPage/ModelEndpoints/ModelEndpointsTable.jsx b/src/components/ModelsPage/ModelEndpoints/ModelEndpointsTable.jsx index 8d15dd38e8..c7a1797fa9 100644 --- a/src/components/ModelsPage/ModelEndpoints/ModelEndpointsTable.jsx +++ b/src/components/ModelsPage/ModelEndpoints/ModelEndpointsTable.jsx @@ -124,6 +124,7 @@ const ModelEndpointsTable = React.forwardRef( const fetchData = useCallback( filters => { + ref.current.abort(REQUEST_CANCELED) ref.current = new AbortController() fetchEndpoints(filters) @@ -276,8 +277,6 @@ const ModelEndpointsTable = React.forwardRef( return ( <> - {(artifactsStore.modelEndpoints.modelEndpointLoading || - artifactsStore.modelEndpoints.loading) && }
@@ -297,7 +296,9 @@ const ModelEndpointsTable = React.forwardRef(
- {artifactsStore.modelEndpoints.loading ? null : modelEndpoints.length === 0 ? ( + {artifactsStore.modelEndpoints.loading ? ( + + ) : modelEndpoints.length === 0 ? ( ) : ( <> + {(artifactsStore.modelEndpoints.modelEndpointLoading || + artifactsStore.modelEndpoints.loading) && }
{
- {artifactsStore.loading && }
diff --git a/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.jsx b/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.jsx index 8e262ce2f2..7bd451034b 100644 --- a/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.jsx +++ b/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.jsx @@ -298,7 +298,6 @@ const RealTimePipelines = () => { return ( <> - {isLoading && }
@@ -335,7 +334,9 @@ const RealTimePipelines = () => { All Serving Pipelines
- {isLoading ? null : pipelines.length === 0 ? ( + {isLoading ? ( + + ) : pipelines.length === 0 ? ( { ) )}
- {isPipelineLoading && } + {isPipelineLoading && } {!isEmpty(selectedPipelineWithChildren) && (
{ const filters = useFiltersFromSearchParams(filtersConfig, undefined, params.projectName) const [, setSearchParams] = useSearchParams() const contentRef = useRef(null) + const abortControllerRef = useRef(new AbortController()) const refreshMonitoringApplications = useCallback( (filters, isFilterApplyAction) => { if (!isFilterApplyAction) { - dispatch(fetchMonitoringApplicationsSummary({ project: params.projectName })) + abortControllerRef.current.abort(REQUEST_CANCELED) + abortControllerRef.current = new AbortController() + + const signal = abortControllerRef.current.signal + + dispatch(fetchMonitoringApplicationsSummary({ project: params.projectName, signal })) .unwrap() .catch(error => { + if (isRequestAborted(error?.message)) return + showErrorNotification(dispatch, error, '', 'Failed to fetch applications summary') }) - dispatch(fetchMonitoringApplications({ project: params.projectName, filters })) + dispatch(fetchMonitoringApplications({ project: params.projectName, filters, signal })) dispatch( fetchMEPWithDetections({ project: params.projectName, - filters: filters + filters: filters, + signal }) ) .unwrap() .catch(error => { + if (isRequestAborted(error?.message)) return + showErrorNotification( dispatch, error, @@ -85,6 +101,11 @@ const MonitoringApplicationsPage = () => { const refreshMonitoringApplication = useCallback( (filters, isFilterApplyAction) => { if (!isFilterApplyAction) { + abortControllerRef.current.abort(REQUEST_CANCELED) + abortControllerRef.current = new AbortController() + + const signal = abortControllerRef.current.signal + dispatch( fetchArtifacts({ project: params.projectName, @@ -92,11 +113,13 @@ const MonitoringApplicationsPage = () => { ...filters, labels: `mlrun/app-name=${params.name}` }, - config: { params: { page: 1, 'page-size': 50, format: 'minimal' } } // limit to 50 artifacts the same as we have on Artifacts page per 1 FE page to avoid overload + config: { signal, params: { page: 1, 'page-size': 50, format: 'minimal' } } // limit to 50 artifacts the same as we have on Artifacts page per 1 FE page to avoid overload }) ) .unwrap() .catch(error => { + if (isRequestAborted(error?.message)) return + showErrorNotification(dispatch, error, '', 'Failed to fetch artifacts') }) @@ -104,11 +127,14 @@ const MonitoringApplicationsPage = () => { fetchMonitoringApplication({ project: params.projectName, functionName: params.name, - filters + filters, + signal }) ) .unwrap() .catch(error => { + if (isRequestAborted(error?.message)) return + showErrorNotification(dispatch, error, '', 'Failed to fetch monitoring application') navigate( `/projects/${params.projectName}/${MONITORING_APP_PAGE}${window.location.search}`, @@ -128,6 +154,12 @@ const MonitoringApplicationsPage = () => { } }, [params.name, refreshMonitoringApplications, refreshMonitoringApplication, filters]) + useEffect(() => { + return () => { + abortControllerRef.current?.abort(REQUEST_CANCELED) + } + }, []) + useEffect(() => { if (contentRef.current) { contentRef.current.scrollTo(0, 0) diff --git a/src/components/Project/ProjectMonitor.jsx b/src/components/Project/ProjectMonitor.jsx index 2c4a49732a..a6095672cd 100644 --- a/src/components/Project/ProjectMonitor.jsx +++ b/src/components/Project/ProjectMonitor.jsx @@ -126,6 +126,9 @@ const ProjectMonitor = () => { }, [navigate, params, openRegisterArtifactModal, openRegisterModelModal, isDemoMode]) const fetchProjectDataAndSummary = useCallback(() => { + projectAbortControllerRef.current.abort(REQUEST_CANCELED) + projectSummariesAbortControllerRef.current.abort(REQUEST_CANCELED) + nuclioFunctionsAbortControllerRef.current.abort(REQUEST_CANCELED) projectAbortControllerRef.current = new AbortController() projectSummariesAbortControllerRef.current = new AbortController() nuclioFunctionsAbortControllerRef.current = new AbortController() diff --git a/src/components/Project/ProjectMonitorView.jsx b/src/components/Project/ProjectMonitorView.jsx index dc51ed26ec..28b5c988ee 100644 --- a/src/components/Project/ProjectMonitorView.jsx +++ b/src/components/Project/ProjectMonitorView.jsx @@ -67,7 +67,7 @@ const ProjectMonitorView = ({ {project.loading ? ( - + ) : project.error ? (
{confirmData ? ( diff --git a/src/components/Project/ProjectOverview/ProjectOverview.jsx b/src/components/Project/ProjectOverview/ProjectOverview.jsx index e702e01ee5..e5c45a160e 100644 --- a/src/components/Project/ProjectOverview/ProjectOverview.jsx +++ b/src/components/Project/ProjectOverview/ProjectOverview.jsx @@ -87,7 +87,7 @@ const ProjectOverview = () => {
- {project.loading && } + {project.loading && } {isEmpty(project.data) && !project.loading ? ( ) : ( diff --git a/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.jsx b/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.jsx index f756c1b00f..d9f3ac3669 100644 --- a/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.jsx +++ b/src/components/ProjectsJobsMonitoring/ProjectsJobsMonitoring.jsx @@ -266,7 +266,7 @@ const ProjectsJobsMonitoring = () => { {(Boolean(jobsStore.jobLoadingCounter) || workflowsStore.activeWorkflow.loading || - functionsStore.funcLoading) && } + functionsStore.funcLoading) && }
)} diff --git a/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx b/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx index 0c8caee658..d57efbcc29 100644 --- a/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx +++ b/src/components/ProjectsPage/CreateProjectDialog/CreateProjectDialog.jsx @@ -72,7 +72,7 @@ const CreateProjectDialog = ({ closeNewProjectPopUp, handleCreateProject, isOpen closePopUp={handleCloseModal} isOpen={isOpen} > - {projectStore.loading && } + {projectStore.loading && }
{formState => { return ( diff --git a/src/components/ProjectsPage/Projects.jsx b/src/components/ProjectsPage/Projects.jsx index 2292fa8b6f..634a18ec9c 100644 --- a/src/components/ProjectsPage/Projects.jsx +++ b/src/components/ProjectsPage/Projects.jsx @@ -36,7 +36,7 @@ import { projectsSortOptions } from './projects.util' import { BG_TASK_RUNNING } from '../../utils/poll.util' -import { PROJECT_ONLINE_STATUS } from '../../constants' +import { PROJECT_ONLINE_STATUS, REQUEST_CANCELED } from '../../constants' import { ConfirmDialog } from 'igz-controls/components' import { openPopUp } from 'igz-controls/utils/common.util' import { @@ -130,6 +130,7 @@ const Projects = () => { ) const refreshProjects = useCallback(() => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() if (!isNuclioModeDisabled) { diff --git a/src/components/ProjectsPage/ProjectsView.jsx b/src/components/ProjectsPage/ProjectsView.jsx index 64522cc25e..94b1b8d660 100644 --- a/src/components/ProjectsPage/ProjectsView.jsx +++ b/src/components/ProjectsPage/ProjectsView.jsx @@ -73,7 +73,7 @@ const ProjectsView = ({ return (
- {(projectStore.loading || projectStore.project.loading || tasksStore.loading) && } + {(projectStore.loading || projectStore.project.loading || tasksStore.loading) && } {projectStore.mlrunUnhealthy.isUnhealthy && ( MLRun seems to be down. Try again in a few minutes. diff --git a/src/elements/AlertsTable/AlertsTable.jsx b/src/elements/AlertsTable/AlertsTable.jsx index c0dc724a18..0716880787 100644 --- a/src/elements/AlertsTable/AlertsTable.jsx +++ b/src/elements/AlertsTable/AlertsTable.jsx @@ -49,7 +49,7 @@ const AlertsTable = ({ return ( <> {alertsStore.loading ? ( - + ) : tableContent.length === 0 && isEmpty(selectedAlert) ? ( ) : ( <> - {alertsStore.alertLoading && } + {alertsStore.alertLoading && } diff --git a/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.jsx b/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.jsx index cc89ed58d9..c391301bb0 100644 --- a/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.jsx +++ b/src/elements/FeatureStoreTableRow/FeatureStoreTableRow.jsx @@ -108,7 +108,7 @@ const FeatureStoreTableRow = ({ {selectedRowData[rowItem.data.ui.identifier]?.loading ? ( ) : selectedRowData[rowItem.data.ui.identifier]?.error ? (
- {createPortal(, document.querySelector(`.${TABLE_CONTAINER}`))} + {createPortal(, document.querySelector(`.${TABLE_CONTAINER}`))} diff --git a/src/elements/JobsTable/JobsTable.jsx b/src/elements/JobsTable/JobsTable.jsx index 4ddb639c8b..f4b02fa52f 100644 --- a/src/elements/JobsTable/JobsTable.jsx +++ b/src/elements/JobsTable/JobsTable.jsx @@ -408,7 +408,7 @@ const JobsTable = React.forwardRef( return ( <> - {jobsStore.loading && } + {jobsStore.loading && } {paginatedJobs.length === 0 && !jobsStore.loading && filters && isEmpty(selectedJob) ? ( - {(projectStore.loading || projectStore.project.loading) && } + {(projectStore.loading || projectStore.project.loading) && } {}}> {formState => { @@ -255,7 +255,7 @@ const ProjectSettingsGeneral = ({ return (
{projectStore.project.loading ? ( - + ) : projectStore.project.error ? (

{projectStore.project.error.message}

diff --git a/src/elements/ProjectSettingsMembers/ProjectSettingsMembers.jsx b/src/elements/ProjectSettingsMembers/ProjectSettingsMembers.jsx index d56b4b79cb..f405355fb9 100644 --- a/src/elements/ProjectSettingsMembers/ProjectSettingsMembers.jsx +++ b/src/elements/ProjectSettingsMembers/ProjectSettingsMembers.jsx @@ -75,7 +75,7 @@ const ProjectSettingsMembers = ({ return (
{loading ? ( - + ) : (
diff --git a/src/elements/ProjectSettingsSecrets/ProjectSettingsSecrets.jsx b/src/elements/ProjectSettingsSecrets/ProjectSettingsSecrets.jsx index 2a74f1dc08..b0821570fc 100644 --- a/src/elements/ProjectSettingsSecrets/ProjectSettingsSecrets.jsx +++ b/src/elements/ProjectSettingsSecrets/ProjectSettingsSecrets.jsx @@ -182,7 +182,7 @@ const ProjectSettingsSecrets = ({ setNotification }) => { return (
{projectStore.project.secrets?.loading ? ( - + ) : !isUserAllowed ? (

You don't have access to this project's secrets

diff --git a/src/elements/ScheduledJobsTable/ScheduledJobsTable.jsx b/src/elements/ScheduledJobsTable/ScheduledJobsTable.jsx index 6be2b8c3e8..b9ff7597ac 100644 --- a/src/elements/ScheduledJobsTable/ScheduledJobsTable.jsx +++ b/src/elements/ScheduledJobsTable/ScheduledJobsTable.jsx @@ -281,7 +281,7 @@ const ScheduledJobsTable = ({ return ( <> - {jobsStore.loading && } + {jobsStore.loading && } {jobsStore.loading ? null : jobs.length === 0 ? ( - {(workflowsStore.workflows.loading || permissionsLoading) && } + {(workflowsStore.workflows.loading || permissionsLoading) && } {workflowsStore.workflows.loading ? null : (!workflowsStore.workflows.loading && !params.workflowId && workflowsStore.workflows.data.length === 0) || diff --git a/src/hooks/useJobsPageData.js b/src/hooks/useJobsPageData.js index 0002ee7de2..336dabeee3 100644 --- a/src/hooks/useJobsPageData.js +++ b/src/hooks/useJobsPageData.js @@ -33,6 +33,7 @@ import { JOBS_MONITORING_JOBS_TAB, JOBS_MONITORING_PAGE, MONITOR_JOBS_TAB, + REQUEST_CANCELED, SCHEDULE_TAB } from '../constants' import { usePagination } from './usePagination.hook' @@ -99,6 +100,7 @@ export const useJobsPageData = (initialTabData, selectedTab) => { setJobs(null) } + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() terminateAbortTasksPolling() @@ -195,6 +197,7 @@ export const useJobsPageData = (initialTabData, selectedTab) => { const refreshScheduled = useCallback( filters => { setScheduledJobs([]) + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() return dispatch( @@ -227,12 +230,16 @@ export const useJobsPageData = (initialTabData, selectedTab) => { setScheduledJobs(parsedJobs) } }) + .catch(() => { + setScheduledJobs([]) + }) }, [dispatch, params.projectName] ) const getWorkflows = useCallback( filters => { + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const projectName = filters.project?.toLowerCase?.() || params.projectName || '*' diff --git a/src/hooks/useRefreshAlerts.hook.js b/src/hooks/useRefreshAlerts.hook.js index a5d50987cc..74fae4e316 100644 --- a/src/hooks/useRefreshAlerts.hook.js +++ b/src/hooks/useRefreshAlerts.hook.js @@ -27,7 +27,8 @@ import { BE_PAGE_SIZE, MODEL_ENDPOINT_ID, PROJECT_FILTER, - PROJECTS_FILTER_ALL_ITEMS + PROJECTS_FILTER_ALL_ITEMS, + REQUEST_CANCELED } from '../constants' import { fetchAlerts } from '../reducers/alertsReducer' @@ -46,6 +47,7 @@ export const useRefreshAlerts = (filters, isAlertsPage) => { filters => { setAlerts(null) lastCheckedAlertIdRef.current = null + abortControllerRef.current.abort(REQUEST_CANCELED) abortControllerRef.current = new AbortController() const projectName = !isAlertsPage ? params.projectName || params.id diff --git a/src/httpClient.js b/src/httpClient.js index a61f075bf2..089fb5daaa 100755 --- a/src/httpClient.js +++ b/src/httpClient.js @@ -93,6 +93,10 @@ let requestTimeouts = {} let largeResponsePopUpIsOpen = false const requestLargeDataOnFulfill = config => { + if (config?.ui?.controller && !config?.ui?.setRequestErrorMessage) { + config.signal = config.ui.controller.signal + } + if (config?.ui?.setRequestErrorMessage) { const [signal, timeoutId] = getAbortSignal( config.ui?.controller, diff --git a/src/nextGenComponents/pages/ApplicationsPage/ApplicationDetails/MonitoringEndpoints/EndpointDetailsDialog.jsx b/src/nextGenComponents/pages/ApplicationsPage/ApplicationDetails/MonitoringEndpoints/EndpointDetailsDialog.jsx index f742660540..9072c6e327 100644 --- a/src/nextGenComponents/pages/ApplicationsPage/ApplicationDetails/MonitoringEndpoints/EndpointDetailsDialog.jsx +++ b/src/nextGenComponents/pages/ApplicationsPage/ApplicationDetails/MonitoringEndpoints/EndpointDetailsDialog.jsx @@ -131,7 +131,7 @@ const EndpointDetailsDialog = ({
{isLoading ? (
- +
) : (
diff --git a/src/nextGenComponents/pages/ApplicationsPage/ApplicationsPage.jsx b/src/nextGenComponents/pages/ApplicationsPage/ApplicationsPage.jsx index 3af7ce6c0e..c7d8742bfc 100644 --- a/src/nextGenComponents/pages/ApplicationsPage/ApplicationsPage.jsx +++ b/src/nextGenComponents/pages/ApplicationsPage/ApplicationsPage.jsx @@ -248,9 +248,9 @@ const ApplicationsPage = () => {
-
+
{isLoading ? ( - + ) : applications.length === 0 && !isDetailsOpen ? ( + return } return ( diff --git a/src/reducers/alertsReducer.js b/src/reducers/alertsReducer.js index 552cc79568..d73dad2855 100644 --- a/src/reducers/alertsReducer.js +++ b/src/reducers/alertsReducer.js @@ -128,12 +128,14 @@ export const fetchAlert = createAsyncThunk( return parseAlerts(data.activations || []) }) .catch(error => { - largeResponseCatchHandler( + const isRequestCanceled = largeResponseCatchHandler( error, 'Failed to fetch alerts', thunkAPI.dispatch, config?.ui?.setRequestErrorMessage ) + + return thunkAPI.rejectWithValue(isRequestCanceled ? { aborted: true } : error) }) } ) @@ -156,12 +158,14 @@ export const fetchAlerts = createAsyncThunk( return { ...data, activations: parseAlerts(data.activations || []) } }) .catch(error => { - largeResponseCatchHandler( + const isRequestCanceled = largeResponseCatchHandler( error, 'Failed to fetch alerts', thunkAPI.dispatch, config?.ui?.setRequestErrorMessage ) + + return thunkAPI.rejectWithValue(isRequestCanceled ? { aborted: true } : error) }) } ) @@ -194,6 +198,7 @@ const alertsSlice = createSlice({ state.loading = false }) .addCase(fetchAlert.rejected, (state, action) => { + if (action.payload?.aborted) return state.alerts = [] state.error = action.payload state.loading = false @@ -207,6 +212,7 @@ const alertsSlice = createSlice({ state.alerts = action.payload }) .addCase(fetchAlerts.rejected, (state, action) => { + if (action.payload?.aborted) return state.loading = false state.error = action.payload }) diff --git a/src/reducers/artifactsReducer.js b/src/reducers/artifactsReducer.js index c5f29b7357..5de5247a30 100644 --- a/src/reducers/artifactsReducer.js +++ b/src/reducers/artifactsReducer.js @@ -30,6 +30,7 @@ import { MODELS_PAGE } from '../constants' import { filterArtifacts } from '../utils/filterArtifacts' +import { isRequestAborted } from '../utils/isRequestAborted' import { generateArtifacts } from '../utils/generateArtifacts' import { parseModelEndpoints } from '../utils/parseModelEndpoints' import { parseArtifacts } from '../utils/parseArtifacts' @@ -571,6 +572,7 @@ const artifactsSlice = createSlice({ state.loading = false }) builder.addCase(fetchArtifacts.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.artifacts = [] state.error = action.payload state.loading = false @@ -627,7 +629,8 @@ const artifactsSlice = createSlice({ state.LLMPrompts.loading = false state.loading = state.models.loading || state.files.loading }) - builder.addCase(fetchLLMPrompts.rejected, state => { + builder.addCase(fetchLLMPrompts.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return state.LLMPrompts.loading = false state.loading = state.models.loading || state.files.loading }) @@ -650,7 +653,8 @@ const artifactsSlice = createSlice({ state.documents.loading = false state.loading = false }) - builder.addCase(fetchDocuments.rejected, state => { + builder.addCase(fetchDocuments.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return state.documents.loading = false state.loading = false }) @@ -660,7 +664,8 @@ const artifactsSlice = createSlice({ state.datasets.loading = false state.loading = state.models.loading || state.files.loading }) - builder.addCase(fetchDataSets.rejected, state => { + builder.addCase(fetchDataSets.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return state.datasets.loading = false state.loading = state.models.loading || state.files.loading }) @@ -683,7 +688,8 @@ const artifactsSlice = createSlice({ state.files.loading = false state.loading = state.models.loading || state.datasets.loading }) - builder.addCase(fetchFiles.rejected, state => { + builder.addCase(fetchFiles.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return state.files.loading = false state.loading = state.models.loading || state.datasets.loading }) @@ -709,10 +715,12 @@ const artifactsSlice = createSlice({ state.modelEndpoints.loading = true }) builder.addCase(fetchModelEndpoints.fulfilled, (state, action) => { + if (action.payload === undefined) return state.error = null state.modelEndpoints = { allData: action.payload, loading: false } }) builder.addCase(fetchModelEndpoints.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.error = action.payload state.modelEndpoints = { allData: [], loading: false } }) @@ -726,7 +734,8 @@ const artifactsSlice = createSlice({ state.models.loading = false state.loading = state.files.loading || state.datasets.loading }) - builder.addCase(fetchModels.rejected, state => { + builder.addCase(fetchModels.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return state.models.loading = false state.loading = state.files.loading || state.datasets.loading }) diff --git a/src/reducers/featureStoreReducer.js b/src/reducers/featureStoreReducer.js index 53deac56ac..ce2dc1c4a2 100644 --- a/src/reducers/featureStoreReducer.js +++ b/src/reducers/featureStoreReducer.js @@ -25,6 +25,7 @@ import { import featureStoreApi from '../api/featureStore-api' import { FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' import { PANEL_DEFAULT_ACCESS_KEY } from '../constants' +import { isRequestAborted } from '../utils/isRequestAborted' import { REDISNOSQL } from '../components/FeatureSetsPanel/FeatureSetsPanelTargetStore/featureSetsPanelTargetStore.util' import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { hideLoading, showLoading } from './redux.util' @@ -183,7 +184,7 @@ export const fetchFeatureSets = createAsyncThunk( config?.ui?.setRequestErrorMessage ) - return thunkAPI.rejectWithValue(error.message) + return thunkAPI.rejectWithValue(error) }) } ) @@ -455,6 +456,7 @@ const featureStoreSlice = createSlice({ }) builder.addCase(fetchFeatureSets.pending, showLoading) builder.addCase(fetchFeatureSets.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.loading = false state.error = action.payload }) @@ -488,6 +490,7 @@ const featureStoreSlice = createSlice({ }) builder.addCase(fetchFeatureVectors.pending, showLoading) builder.addCase(fetchFeatureVectors.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.loading = false state.error = action.payload }) @@ -505,6 +508,7 @@ const featureStoreSlice = createSlice({ state.features.loading = true }) builder.addCase(fetchFeatures.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.features.loading = false state.error = action.payload }) diff --git a/src/reducers/functionReducer.js b/src/reducers/functionReducer.js index 463cf83489..bf9799cbf9 100644 --- a/src/reducers/functionReducer.js +++ b/src/reducers/functionReducer.js @@ -18,6 +18,7 @@ under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ import { FUNCTION_TYPE_JOB, PANEL_DEFAULT_ACCESS_KEY } from '../constants' +import { isRequestAborted } from '../utils/isRequestAborted' import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' import functionsApi from '../api/functions-api' @@ -401,6 +402,7 @@ const functionsSlice = createSlice({ state.functions = action.payload.funcs }) builder.addCase(fetchFunctions.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.error = action.payload state.loading = false state.functions = [] diff --git a/src/reducers/jobReducer.js b/src/reducers/jobReducer.js index 4d0a98bccb..f5c54c4582 100644 --- a/src/reducers/jobReducer.js +++ b/src/reducers/jobReducer.js @@ -162,12 +162,14 @@ export const fetchAllJobRuns = createAsyncThunk( return data }) .catch(error => { - largeResponseCatchHandler( + const isRequestCanceled = largeResponseCatchHandler( error, 'Failed to fetch jobs', thunkAPI.dispatch, config?.ui?.setRequestErrorMessage ) + + return thunkAPI.rejectWithValue(isRequestCanceled ? { aborted: true } : error) }) } ) @@ -218,12 +220,14 @@ export const fetchJobs = createAsyncThunk('fetchJobs', ({ project, filters, conf return data }) .catch(error => { - largeResponseCatchHandler( + const isRequestCanceled = largeResponseCatchHandler( error, 'Failed to fetch jobs', thunkAPI.dispatch, config?.ui?.setRequestErrorMessage ) + + return thunkAPI.rejectWithValue(isRequestCanceled ? { aborted: true } : error) }) }) export const fetchScheduledJobs = createAsyncThunk( @@ -267,12 +271,14 @@ export const fetchScheduledJobs = createAsyncThunk( return (data || {}).schedules }) .catch(error => { - largeResponseCatchHandler( + const isRequestCanceled = largeResponseCatchHandler( error, 'Failed to fetch scheduled jobs', thunkAPI.dispatch, config?.ui?.setRequestErrorMessage ) + + return thunkAPI.rejectWithValue(isRequestCanceled ? { aborted: true } : error) }) } ) @@ -344,7 +350,12 @@ const jobsSlice = createSlice({ state.jobRuns = action.payload state.loading = false }) - builder.addCase(fetchAllJobRuns.rejected, hideLoading) + builder.addCase(fetchAllJobRuns.rejected, (state, action) => { + if (action.payload?.aborted) return + state.error = action.payload + state.jobRuns = [] + state.loading = false + }) builder.addCase(fetchJob.pending, state => { state.jobLoadingCounter++ }) @@ -400,6 +411,7 @@ const jobsSlice = createSlice({ state.loading = false }) builder.addCase(fetchJobs.rejected, (state, action) => { + if (action.payload?.aborted) return state.error = action.payload state.jobs = [] state.loading = false @@ -411,6 +423,7 @@ const jobsSlice = createSlice({ state.loading = false }) builder.addCase(fetchScheduledJobs.rejected, (state, action) => { + if (action.payload?.aborted) return state.error = action.payload state.scheduled = [] state.loading = false diff --git a/src/reducers/monitoringApplicationsReducer.js b/src/reducers/monitoringApplicationsReducer.js index a15f432bbd..0337061ef6 100644 --- a/src/reducers/monitoringApplicationsReducer.js +++ b/src/reducers/monitoringApplicationsReducer.js @@ -20,12 +20,13 @@ such restriction. import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import { get } from 'lodash' -import { defaultPendingHandler, defaultRejectedHandler } from './redux.util' +import { defaultPendingHandler } from './redux.util' import { splitApplicationsContent } from '../utils/applications.utils' import { largeResponseCatchHandler } from '../utils/largeResponseCatchHandler' import { getErrorMsg } from 'igz-controls/utils/common.util' import { DATES_FILTER } from '../constants' +import { isRequestAborted } from '../utils/isRequestAborted' import monitoringApplicationsApi from '../api/monitoringApplications-api' import nuclioApi from '../api/nuclio' @@ -55,7 +56,7 @@ const initialState = { export const fetchMEPWithDetections = createAsyncThunk( 'fetchMEPWithDetections', - ({ project, filters }) => { + ({ project, filters, signal }) => { const params = { start: filters[DATES_FILTER].value[0].getTime() } @@ -67,7 +68,7 @@ export const fetchMEPWithDetections = createAsyncThunk( const savedStartDate = filters[DATES_FILTER].value[0].getTime() const savedEndDate = (filters[DATES_FILTER].value[1] || new Date()).getTime() - return monitoringApplicationsApi.getMEPWithDetections(project, params).then(response => { + return monitoringApplicationsApi.getMEPWithDetections(project, params, signal).then(response => { return { values: response.data.values.map(([date, suspected, detected]) => [ date, @@ -82,7 +83,7 @@ export const fetchMEPWithDetections = createAsyncThunk( export const fetchMonitoringApplication = createAsyncThunk( 'fetchMonitoringApplication', - ({ project, functionName, filters }) => { + ({ project, functionName, filters, signal }) => { const params = { start: filters[DATES_FILTER].value[0].getTime() } @@ -92,14 +93,14 @@ export const fetchMonitoringApplication = createAsyncThunk( } return monitoringApplicationsApi - .getMonitoringApplication(project, functionName, params) + .getMonitoringApplication(project, functionName, params, signal) .then(response => response.data) } ) export const fetchMonitoringApplications = createAsyncThunk( 'fetchMonitoringApplications', - async ({ project, filters }, thunkAPI) => { + async ({ project, filters, signal }, thunkAPI) => { const params = { start: filters[DATES_FILTER].value[0].getTime() } @@ -109,18 +110,20 @@ export const fetchMonitoringApplications = createAsyncThunk( } const [mlrunResult, nuclioResult] = await Promise.allSettled([ - monitoringApplicationsApi.getMonitoringApplications(project, params), - nuclioApi.getFunctions(project) + monitoringApplicationsApi.getMonitoringApplications(project, params, signal), + nuclioApi.getFunctions(project, signal) ]) if (mlrunResult.status !== 'fulfilled') { - largeResponseCatchHandler( + const isCanceled = largeResponseCatchHandler( mlrunResult.reason, 'Failed to fetch monitoring applications', thunkAPI.dispatch ) - return thunkAPI.rejectWithValue(getErrorMsg(mlrunResult.reason)) + return thunkAPI.rejectWithValue( + isCanceled ? { aborted: true } : getErrorMsg(mlrunResult.reason) + ) } const mlrunApiApps = get(mlrunResult, 'value.data') @@ -143,9 +146,9 @@ export const fetchMonitoringApplications = createAsyncThunk( export const fetchMonitoringApplicationsSummary = createAsyncThunk( 'fetchMonitoringApplicationsSummary', - ({ project }) => { + ({ project, signal }) => { return monitoringApplicationsApi - .getMonitoringApplicationsSummary(project) + .getMonitoringApplicationsSummary(project, signal) .then(response => response.data) } ) @@ -174,6 +177,8 @@ const monitoringApplicationsSlice = createSlice({ state.endpointsWithDetections.error = null }) builder.addCase(fetchMEPWithDetections.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return + state.endpointsWithDetections.loading = false state.endpointsWithDetections.error = action.error }) @@ -183,7 +188,12 @@ const monitoringApplicationsSlice = createSlice({ state.loading = false state.error = null }) - builder.addCase(fetchMonitoringApplication.rejected, defaultRejectedHandler) + builder.addCase(fetchMonitoringApplication.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return + + state.loading = false + state.error = action.error + }) builder.addCase(fetchMonitoringApplications.pending, defaultPendingHandler) builder.addCase(fetchMonitoringApplications.fulfilled, (state, { payload }) => { state.monitoringApplications = payload @@ -191,6 +201,8 @@ const monitoringApplicationsSlice = createSlice({ state.error = null }) builder.addCase(fetchMonitoringApplications.rejected, (state, action) => { + if (action.payload?.aborted) return + state.loading = false state.error = action.payload }) @@ -203,6 +215,8 @@ const monitoringApplicationsSlice = createSlice({ state.applicationsSummary.error = null }) builder.addCase(fetchMonitoringApplicationsSummary.rejected, (state, action) => { + if (isRequestAborted(action.error?.message)) return + state.applicationsSummary.loading = false state.applicationsSummary.error = action.error }) diff --git a/src/reducers/nuclioReducer.js b/src/reducers/nuclioReducer.js index 005f2d7060..36fbbcc43b 100644 --- a/src/reducers/nuclioReducer.js +++ b/src/reducers/nuclioReducer.js @@ -24,8 +24,8 @@ import nuclioApi from '../api/nuclio' import functionsApi from '../api/functions-api' import { parseV3ioStreams } from '../utils/parseV3ioStreams' import { parseV3ioStreamShardLags } from '../utils/parseV3ioStreamShardLags' -import { DEFAULT_ABORT_MSG, REQUEST_CANCELED } from '../constants' import { showErrorNotification } from 'igz-controls/utils/notification.util' +import { isRequestAborted } from '../utils/isRequestAborted' export const fetchApiGateways = createAsyncThunk( 'fetchApiGateways', @@ -117,7 +117,7 @@ export const fetchProjectApiGateways = createAsyncThunk( return [] }) .catch(error => { - if (![REQUEST_CANCELED, DEFAULT_ABORT_MSG].includes(error?.message)) { + if (!isRequestAborted(error?.message)) { showErrorNotification(dispatch, error, 'Failed to load API gateways') return rejectWithValue(error) } @@ -185,7 +185,7 @@ const nuclioSlice = createSlice({ builder.addCase(fetchApiGateways.rejected, (state, action) => { state.apiGateways = 0 state.loading = false - state.error = action.error?.message + state.error = action.payload?.message }) builder.addCase(fetchNuclioFunctions.pending, state => { state.loading = true @@ -196,9 +196,10 @@ const nuclioSlice = createSlice({ state.error = null }) builder.addCase(fetchNuclioFunctions.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.currentProjectFunctions = [] state.loading = false - state.error = action.error?.message + state.error = action.payload?.message }) builder.addCase(fetchAllNuclioFunctions.pending, state => { state.loading = true @@ -209,9 +210,10 @@ const nuclioSlice = createSlice({ state.error = null }) builder.addCase(fetchAllNuclioFunctions.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.functions = {} state.loading = false - state.error = action.error?.message + state.error = action.payload?.message }) builder.addCase(fetchNuclioFunction.pending, state => { state.nuclioFunctionLoading = true diff --git a/src/reducers/workflowReducer.js b/src/reducers/workflowReducer.js index 8046b80cc0..24431acd5e 100644 --- a/src/reducers/workflowReducer.js +++ b/src/reducers/workflowReducer.js @@ -24,6 +24,7 @@ import { isNil } from 'lodash' import { largeResponseCatchHandler } from '../utils/largeResponseCatchHandler' import { parseWorkflows } from '../utils/parseWorkflows' import { JOBS_MONITORING_WORKFLOWS_TAB, MONITOR_WORKFLOWS_TAB } from '../constants' +import { isRequestAborted } from '../utils/isRequestAborted' const initialState = { workflows: { @@ -140,6 +141,7 @@ const workflowsSlice = createSlice({ } }) builder.addCase(fetchWorkflows.rejected, (state, action) => { + if (isRequestAborted(action.payload?.message)) return state.workflows = { data: [], loading: false, diff --git a/src/utils/getArtifactPreview.jsx b/src/utils/getArtifactPreview.jsx index 5748253458..cce08437aa 100644 --- a/src/utils/getArtifactPreview.jsx +++ b/src/utils/getArtifactPreview.jsx @@ -24,11 +24,10 @@ import api from '../api/artifacts-api' import { createArtifactPreviewContent } from './createArtifactPreviewContent' import { ARTIFACT_MAX_CHUNK_SIZE, - DEFAULT_ABORT_MSG, ERROR_STATE, - REQUEST_CANCELED, UNKNOWN_STATE } from '../constants' +import { isRequestAborted } from './isRequestAborted' import { commonLanguages } from '../common/Editor/editor.util' const fileSizes = { @@ -220,7 +219,7 @@ export const fetchArtifactPreviewFromPath = async ( return setPreview([createArtifactPreviewContent(null, null, path, artifact.db_key)]) } } catch (err) { - if (![REQUEST_CANCELED, DEFAULT_ABORT_MSG].includes(err.message)) { + if (!isRequestAborted(err.message)) { setPreview([ { error: { diff --git a/src/utils/isRequestAborted.js b/src/utils/isRequestAborted.js new file mode 100644 index 0000000000..45d6730006 --- /dev/null +++ b/src/utils/isRequestAborted.js @@ -0,0 +1,23 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import { DEFAULT_ABORT_MSG, REQUEST_CANCELED } from '../constants' + +export const isRequestAborted = message => + [REQUEST_CANCELED, DEFAULT_ABORT_MSG].includes(message) diff --git a/src/utils/largeResponseCatchHandler.js b/src/utils/largeResponseCatchHandler.js index 86a314d8cd..79e734f04d 100644 --- a/src/utils/largeResponseCatchHandler.js +++ b/src/utils/largeResponseCatchHandler.js @@ -26,11 +26,13 @@ export const largeResponseCatchHandler = ( dispatch, showNotificationCallback = () => {} ) => { - if ( - ![LARGE_REQUEST_CANCELED, REQUEST_CANCELED, DEFAULT_ABORT_MSG].includes(error.message) && - error && - dispatch - ) { + const isRequestCanceled = [LARGE_REQUEST_CANCELED, REQUEST_CANCELED, DEFAULT_ABORT_MSG].includes( + error?.message + ) + + if (!isRequestCanceled && error && dispatch) { showErrorNotification(dispatch, error, defaultError, null, null, showNotificationCallback) } + + return isRequestCanceled }