Skip to content

Commit 43450b8

Browse files
Merge pull request #2682 from devtron-labs/fix/external-apps-abort-controller
fix: external apps data getting merged with previously opened app in case of delayed response of app details/resource tree
2 parents d0609fe + 82ecf83 commit 43450b8

File tree

13 files changed

+280
-111
lines changed

13 files changed

+280
-111
lines changed

src/Pages/App/Details/ExternalFlux/ExternalFluxAppDetails.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useEffect, useState } from 'react'
17+
import { useEffect, useRef, useState } from 'react'
1818
import { useParams } from 'react-router-dom'
1919

2020
import {
21+
abortPreviousRequests,
2122
AppType,
2223
DeploymentAppTypes,
2324
ERROR_STATUS_CODE,
2425
ErrorScreenManager,
26+
getIsRequestAborted,
2527
IndexStore,
2628
noop,
2729
ResponseType,
@@ -46,6 +48,15 @@ const ExternalFluxAppDetails = () => {
4648
const [isReloadResourceTreeInProgress, setIsReloadResourceTreeInProgress] = useState(true)
4749
const [appDetailsError, setAppDetailsError] = useState(null)
4850

51+
const abortControllerRef = useRef<AbortController>(new AbortController())
52+
53+
useEffect(
54+
() => () => {
55+
abortControllerRef.current.abort()
56+
},
57+
[],
58+
)
59+
4960
const handleUpdateIndexStoreWithDetails = (response: ResponseType<any>) => {
5061
const genericAppDetail: AppDetails = {
5162
...response.result,
@@ -63,13 +74,19 @@ const ExternalFluxAppDetails = () => {
6374
new Promise<void>((resolve) => {
6475
setIsReloadResourceTreeInProgress(true)
6576

66-
getExternalFluxCDAppDetails(clusterId, namespace, appName, isKustomization)
77+
abortPreviousRequests(
78+
() =>
79+
getExternalFluxCDAppDetails({ clusterId, namespace, appName, isKustomization, abortControllerRef }),
80+
abortControllerRef,
81+
)
6782
.then(handleUpdateIndexStoreWithDetails)
6883
.catch((error) => {
69-
if (!initialLoading) {
70-
showError(error)
71-
} else {
72-
setAppDetailsError(error)
84+
if (!getIsRequestAborted(error)) {
85+
if (!initialLoading) {
86+
showError(error)
87+
} else {
88+
setAppDetailsError(error)
89+
}
7390
}
7491
})
7592
.finally(() => {

src/Pages/App/Details/ExternalFlux/service.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@
1717
import { get, getUrlWithSearchParams } from '@devtron-labs/devtron-fe-common-lib'
1818

1919
import { Routes } from '../../../../config'
20+
import { GetExternalFluxCDAppDetailsParamsType } from './types'
2021

21-
export const getExternalFluxCDAppDetails = (clusterId, namespace, appName, isKustomization) => {
22+
export const getExternalFluxCDAppDetails = async ({
23+
clusterId,
24+
namespace,
25+
appName,
26+
isKustomization,
27+
abortControllerRef,
28+
}: GetExternalFluxCDAppDetailsParamsType) => {
2229
const appId = `${clusterId}|${namespace}|${appName}|${isKustomization}`
2330
const baseurl = `${Routes.FLUX_APPS}/${Routes.APP}`
2431
const params = {
2532
appId,
2633
}
34+
2735
const url = getUrlWithSearchParams(baseurl, params)
28-
return get(url)
36+
return get(url, { abortControllerRef })
2937
}

src/Pages/App/Details/ExternalFlux/types.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { APIOptions } from '@devtron-labs/devtron-fe-common-lib'
18+
1719
export interface ExternalFluxAppDetailParams {
1820
clusterId: string
1921
appName: string
@@ -25,3 +27,9 @@ export enum EXTERNAL_FLUX_APP_STATUS {
2527
READY = 'Ready',
2628
NOT_READY = 'Not Ready',
2729
}
30+
31+
export interface GetExternalFluxCDAppDetailsParamsType
32+
extends Pick<ExternalFluxAppDetailParams, 'clusterId' | 'namespace' | 'appName'>,
33+
Pick<APIOptions, 'abortControllerRef'> {
34+
isKustomization: boolean
35+
}

src/components/ClusterNodes/ClusterList/ClusterSelectionBody.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React, { useRef, useState } from 'react'
17+
import React, { useState } from 'react'
1818

1919
import {
2020
BulkSelectionEvents,
@@ -50,9 +50,8 @@ const ClusterSelectionBody: React.FC<ClusterSelectionBodyTypes> = ({
5050
clusterOptions,
5151
clusterListLoader,
5252
filteredList,
53+
parentRef,
5354
}) => {
54-
const parentRef = useRef<HTMLDivElement>(null)
55-
5655
const [showKubeConfigModal, setShowKubeConfigModal] = useState(false)
5756
const [selectedClusterName, setSelectedClusterName] = useState('')
5857

@@ -133,17 +132,19 @@ const ClusterSelectionBody: React.FC<ClusterSelectionBodyTypes> = ({
133132
}
134133

135134
return (
136-
<div ref={parentRef} className="flexbox-col flex-grow-1">
135+
<>
137136
{renderClusterBulkSelection()}
138-
{renderClusterList()}
139-
{showKubeConfigModal && KubeConfigModal && (
140-
<KubeConfigModal
141-
clusterName={selectedClusterName || identifierCount === 0}
142-
handleModalClose={onChangeCloseKubeConfigModal}
143-
isSingleClusterButton={!!selectedClusterName}
144-
/>
145-
)}
146-
</div>
137+
<div className="flexbox-col flex-grow-1">
138+
{renderClusterList()}
139+
{showKubeConfigModal && KubeConfigModal && (
140+
<KubeConfigModal
141+
clusterName={selectedClusterName || identifierCount === 0}
142+
handleModalClose={onChangeCloseKubeConfigModal}
143+
isSingleClusterButton={!!selectedClusterName}
144+
/>
145+
)}
146+
</div>
147+
</>
147148
)
148149
}
149150

src/components/ClusterNodes/ClusterList/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { MutableRefObject } from 'react'
2+
13
import { ClusterDetail } from '@devtron-labs/devtron-fe-common-lib'
24

35
export interface ClusterViewType {
46
clusterOptions: ClusterDetail[]
57
clusterListLoader: boolean
68
initialLoading: boolean
79
refreshData: () => void
10+
parentRef: MutableRefObject<HTMLDivElement>
811
}
912

1013
export interface ClusterListTypes {

src/components/ResourceBrowser/ResourceBrowser.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { sortObjectArrayAlphabetically } from '../common'
3434
import { AddClusterButton } from './PageHeader.buttons'
3535

3636
const ResourceBrowser: React.FC = () => {
37+
const parentRef = useRef<HTMLDivElement>(null)
3738
const abortControllerRef = useRef<AbortController>(new AbortController())
3839

3940
const [detailClusterListLoading, detailClusterList, , reloadDetailClusterList] = useAsync(async () => {
@@ -72,6 +73,7 @@ const ResourceBrowser: React.FC = () => {
7273

7374
return (
7475
<ClusterListView
76+
parentRef={parentRef}
7577
clusterOptions={sortedClusterList}
7678
clusterListLoader={detailClusterListLoading}
7779
initialLoading={initialLoading}
@@ -85,7 +87,7 @@ const ResourceBrowser: React.FC = () => {
8587
}
8688

8789
return (
88-
<div className="flexbox-col h-100 bg__primary">
90+
<div className="flexbox-col h-100 bg__primary" ref={parentRef}>
8991
<PageHeader
9092
isBreadcrumbs={false}
9193
headerName="Kubernetes Resource Browser"

src/components/external-apps/ExternalAppService.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,21 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { get, put, trash, ResponseType, AppType, getUrlWithSearchParams, getAPIOptionsWithTriggerTimeout } from '@devtron-labs/devtron-fe-common-lib'
17+
import {
18+
get,
19+
put,
20+
trash,
21+
ResponseType,
22+
AppType,
23+
getUrlWithSearchParams,
24+
getAPIOptionsWithTriggerTimeout,
25+
APIOptions,
26+
} from '@devtron-labs/devtron-fe-common-lib'
1827
import { Routes } from '../../config'
1928
import { HelmApp, AppEnvironmentDetail } from '../app/list-new/AppListType'
2029
import { ResourceTree } from '../v2/appDetails/appDetails.type'
2130
import { getK8sResourcePayloadAppType } from '@Components/v2/appDetails/k8Resource/nodeDetail/nodeDetail.util'
31+
import { GetArgoAppDetailParamsType } from './types'
2232

2333
export interface ReleaseInfoResponse extends ResponseType {
2434
result?: ReleaseAndInstalledAppInfo
@@ -132,14 +142,20 @@ export const getReleaseInfo = (appId: string): Promise<ReleaseInfoResponse> => {
132142
return get(url)
133143
}
134144

135-
export const getAppDetail = (appId: string): Promise<HelmAppDetailResponse> => {
136-
const url = `${Routes.HELM_RELEASE_APP_DETAIL_API}?appId=${appId}`
137-
return get(url)
138-
}
139-
140-
export const getArgoAppDetail = (appName: string, clusterId: string, namespace: string) => {
141-
return get(`${Routes.ARGO_APPLICATION}?name=${appName}&clusterId=${clusterId}&namespace=${namespace}`)
142-
}
145+
export const getAppDetail = async (
146+
appId: string,
147+
abortControllerRef?: APIOptions['abortControllerRef'],
148+
): Promise<HelmAppDetailResponse> => get(`${Routes.HELM_RELEASE_APP_DETAIL_API}?appId=${appId}`, { abortControllerRef })
149+
150+
export const getArgoAppDetail = async ({
151+
appName,
152+
clusterId,
153+
namespace,
154+
abortControllerRef,
155+
}: GetArgoAppDetailParamsType) =>
156+
get(`${Routes.ARGO_APPLICATION}?name=${appName}&clusterId=${clusterId}&namespace=${namespace}`, {
157+
abortControllerRef,
158+
})
143159

144160
export const deleteApplicationRelease = (appId: string): Promise<UninstallReleaseResponse> => {
145161
const url = `${Routes.HELM_RELEASE_APP_DELETE_API}?appId=${appId}`

src/components/external-apps/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { APIOptions } from '@devtron-labs/devtron-fe-common-lib'
2+
3+
export interface GetArgoAppDetailParamsType extends Pick<APIOptions, 'abortControllerRef'> {
4+
appName: string
5+
clusterId: string
6+
namespace: string
7+
}

src/components/externalArgoApps/ExternalArgoAppDetail.tsx

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React, { useState, useEffect } from 'react'
17+
import { useState, useEffect, useRef } from 'react'
1818
import { useLocation, useHistory } from 'react-router-dom'
1919
import {
2020
showError,
2121
Progressing,
2222
ErrorScreenManager,
2323
ServerErrors,
2424
DeploymentAppTypes,
25+
getIsRequestAborted,
26+
abortPreviousRequests,
2527
} from '@devtron-labs/devtron-fe-common-lib'
2628
import { getArgoAppDetail } from '../external-apps/ExternalAppService'
2729
import { checkIfToRefetchData, deleteRefetchDataFromUrl } from '../util/URLUtil'
@@ -40,10 +42,14 @@ const ExternalArgoAppDetail = ({ appName, clusterId, isExternalApp, namespace }:
4042
let initTimer = null
4143
let isAPICallInProgress = false
4244

45+
const abortControllerRef = useRef<AbortController>(new AbortController())
46+
4347
// component load
4448
useEffect(() => {
4549
_init()
4650
return (): void => {
51+
abortControllerRef.current.abort()
52+
4753
if (initTimer) {
4854
clearTimeout(initTimer)
4955
}
@@ -62,17 +68,18 @@ const ExternalArgoAppDetail = ({ appName, clusterId, isExternalApp, namespace }:
6268

6369
const _init = () => {
6470
if (!isAPICallInProgress) {
65-
_getAndSetAppDetail()
71+
_getAndSetAppDetail(true)
6672
}
67-
initTimer = setTimeout(() => {
68-
_init()
69-
}, window._env_.EA_APP_DETAILS_POLLING_INTERVAL || 30000)
7073
}
7174

72-
const _getAndSetAppDetail = async () => {
75+
const _getAndSetAppDetail = async (shouldTriggerPolling: boolean = false) => {
7376
isAPICallInProgress = true
7477
setIsReloadResourceTreeInProgress(true)
75-
getArgoAppDetail(appName, clusterId, namespace)
78+
79+
abortPreviousRequests(
80+
() => getArgoAppDetail({ appName, clusterId, namespace, abortControllerRef }),
81+
abortControllerRef,
82+
)
7683
.then((appDetailResponse) => {
7784
const genericAppDetail: AppDetails = {
7885
...appDetailResponse.result,
@@ -82,13 +89,21 @@ const ExternalArgoAppDetail = ({ appName, clusterId, isExternalApp, namespace }:
8289
setErrorResponseCode(undefined)
8390
})
8491
.catch((errors: ServerErrors) => {
85-
showError(errors)
86-
setErrorResponseCode(errors.code)
92+
if (!getIsRequestAborted(errors)) {
93+
showError(errors)
94+
setErrorResponseCode(errors.code)
95+
}
8796
})
8897
.finally(() => {
8998
setIsLoading(false)
9099
isAPICallInProgress = false
91100
setIsReloadResourceTreeInProgress(false)
101+
102+
if (shouldTriggerPolling) {
103+
initTimer = setTimeout(() => {
104+
_init()
105+
}, window._env_.EA_APP_DETAILS_POLLING_INTERVAL || 30000)
106+
}
92107
})
93108
}
94109

src/components/v2/appDetails/appDetails.api.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,46 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { get, post, ROUTES } from '@devtron-labs/devtron-fe-common-lib'
17+
import { APIOptions, get, post, ROUTES } from '@devtron-labs/devtron-fe-common-lib'
1818
import { Routes } from '../../../config/constants'
1919
import { AppType } from './appDetails.type'
2020
import { getAppId, generateDevtronAppIdentiferForK8sRequest } from './k8Resource/nodeDetail/nodeDetail.api'
2121
import { getDeploymentType, getK8sResourcePayloadAppType } from './k8Resource/nodeDetail/nodeDetail.util'
2222

23-
export const getInstalledChartDetail = (_appId: number, _envId: number) => {
24-
return get(`${Routes.APP_STORE_INSTALLED_APP}/detail/v2?installed-app-id=${_appId}&env-id=${_envId}`)
25-
}
23+
export const getInstalledChartDetail = (
24+
_appId: number,
25+
_envId: number,
26+
abortControllerRef?: APIOptions['abortControllerRef'],
27+
) =>
28+
get(`${Routes.APP_STORE_INSTALLED_APP}/detail/v2?installed-app-id=${_appId}&env-id=${_envId}`, {
29+
abortControllerRef,
30+
})
2631

27-
export const getInstalledChartResourceTree = (_appId: number, _envId: number) => {
28-
return get(`${Routes.APP_STORE_INSTALLED_APP}/detail/resource-tree?installed-app-id=${_appId}&env-id=${_envId}`)
29-
}
32+
export const getInstalledChartResourceTree = (
33+
_appId: number,
34+
_envId: number,
35+
abortControllerRef?: APIOptions['abortControllerRef'],
36+
) =>
37+
get(`${Routes.APP_STORE_INSTALLED_APP}/detail/resource-tree?installed-app-id=${_appId}&env-id=${_envId}`, {
38+
abortControllerRef,
39+
})
3040

3141
export const getInstalledChartNotesDetail = (_appId: number, _envId: number) => {
3242
if (isNaN(Number(_appId)) || isNaN(Number(_envId))) {
3343
return null
3444
}
35-
45+
3646
return get(`${Routes.APP_STORE_INSTALLED_APP}/notes?installed-app-id=${_appId}&env-id=${_envId}`)
3747
}
3848

39-
export const getInstalledChartDetailWithResourceTree = (_appId: number, _envId: number) => {
40-
return get(`${Routes.APP_STORE_INSTALLED_APP}/resource/hibernate?installed-app-id=${_appId}&env-id=${_envId}`)
41-
}
49+
export const getInstalledChartDetailWithResourceTree = (
50+
_appId: number,
51+
_envId: number,
52+
abortControllerRef?: APIOptions['abortControllerRef'],
53+
) =>
54+
get(`${Routes.APP_STORE_INSTALLED_APP}/resource/hibernate?installed-app-id=${_appId}&env-id=${_envId}`, {
55+
abortControllerRef,
56+
})
4257

4358
export const getInstalledAppDetail = (_appId: number, _envId: number) => {
4459
return get(`app/detail?app-id=${_appId}&env-id=${_envId}`)

0 commit comments

Comments
 (0)