Skip to content

Commit 1d4fe5c

Browse files
authored
Merge pull request #209 from devtron-labs/feat/use-download
feat: add hook use download
2 parents 20e9345 + eb17c17 commit 1d4fe5c

File tree

13 files changed

+123
-23
lines changed

13 files changed

+123
-23
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "0.1.12",
3+
"version": "0.1.13",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Common/Constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@ export const ZERO_TIME_STRING = '0001-01-01T00:00:00Z'
477477
export const EXCLUDED_FALSY_VALUES = [undefined, null, '', NaN] as const
478478

479479
export const API_STATUS_CODES = {
480+
OK: 200,
481+
NO_CONTENT: 204,
480482
BAD_REQUEST: 400,
481483
UNAUTHORIZED: 401,
482484
PERMISSION_DENIED: 403,

src/Shared/Components/CICDHistory/Artifacts.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
import { useEffect, useState } from 'react'
1818
import { useParams } from 'react-router'
19+
import { useDownload } from '@Shared/Hooks'
1920
import {
20-
showError,
2121
GenericEmptyState,
2222
ImageTagsContainer,
2323
ClipboardButton,
@@ -122,7 +122,7 @@ const Artifacts = ({
122122
artifact,
123123
blobStorageEnabled,
124124
isArtifactUploaded,
125-
getArtifactPromise,
125+
downloadArtifactUrl,
126126
ciPipelineId,
127127
artifactId,
128128
isJobView,
@@ -137,6 +137,7 @@ const Artifacts = ({
137137
renderCIListHeader,
138138
}: ArtifactType) => {
139139
const { isSuperAdmin } = useSuperAdmin()
140+
const { handleDownload } = useDownload()
140141

141142
const { triggerId, buildId } = useParams<{
142143
triggerId: string
@@ -152,16 +153,10 @@ const Artifacts = ({
152153
}, [copied])
153154

154155
async function handleArtifact() {
155-
try {
156-
const response = await getArtifactPromise()
157-
const b = await (response as any).blob()
158-
const a = document.createElement('a')
159-
a.href = URL.createObjectURL(b)
160-
a.download = `${buildId || triggerId}.zip`
161-
a.click()
162-
} catch (err) {
163-
showError(err)
164-
}
156+
await handleDownload({
157+
downloadUrl: downloadArtifactUrl,
158+
fileName: `${buildId || triggerId}.zip`,
159+
})
165160
}
166161

167162
if (status.toLowerCase() === TERMINAL_STATUS_MAP.RUNNING || status.toLowerCase() === TERMINAL_STATUS_MAP.STARTING) {
@@ -251,7 +246,7 @@ const Artifacts = ({
251246
</CIListItem>
252247
)}
253248
{blobStorageEnabled &&
254-
getArtifactPromise &&
249+
downloadArtifactUrl &&
255250
(type === HistoryComponentType.CD || isArtifactUploaded || isJobView) && (
256251
<CIListItem
257252
type="report"

src/Shared/Components/CICDHistory/TriggerOutput.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import {
6363
VirtualHistoryArtifactProps,
6464
RunSourceType,
6565
} from './types'
66-
import { getTagDetails, getTriggerDetails, cancelCiTrigger, cancelPrePostCdTrigger, getCDBuildReport } from './service'
66+
import { getTagDetails, getTriggerDetails, cancelCiTrigger, cancelPrePostCdTrigger } from './service'
6767
import { DEFAULT_ENV, TIMEOUT_VALUE, WORKER_POD_BASE_URL } from './constants'
6868
import { GitTriggers } from '../../types'
6969
import warn from '../../../Assets/Icon/ic-warning.svg'
@@ -561,6 +561,8 @@ const HistoryLogs: React.FC<{
561561
workflowId: triggerDetails.id,
562562
}
563563

564+
const CDBuildReportUrl = `app/cd-pipeline/workflow/download/${appId}/${envId}/${pipelineId}/${triggerId}`
565+
564566
const [ref, scrollToTop, scrollToBottom] = useScrollable({
565567
autoBottomScroll: triggerDetails.status.toLowerCase() !== 'succeeded',
566568
})
@@ -665,7 +667,7 @@ const HistoryLogs: React.FC<{
665667
tagsEditable={tagsEditable}
666668
appReleaseTagNames={appReleaseTags}
667669
hideImageTaggingHardDelete={hideImageTaggingHardDelete}
668-
getArtifactPromise={() => getCDBuildReport(appId, envId, pipelineId, triggerId)}
670+
downloadArtifactUrl={CDBuildReportUrl}
669671
type={HistoryComponentType.CD}
670672
renderCIListHeader={renderCIListHeader}
671673
/>

src/Shared/Components/CICDHistory/service.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,6 @@ export const getDeploymentDiffSelector = (
268268
return get(url)
269269
}
270270

271-
export function getCDBuildReport(appId, envId, pipelineId, workflowId) {
272-
return get(`app/cd-pipeline/workflow/download/${appId}/${envId}/${pipelineId}/${workflowId}`)
273-
}
274-
275271
export const getTriggerHistory = async ({
276272
appId,
277273
envId,

src/Shared/Components/CICDHistory/types.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ export interface ArtifactType {
551551
artifact: string
552552
blobStorageEnabled: boolean
553553
isArtifactUploaded?: boolean
554-
getArtifactPromise?: () => Promise<any>
554+
downloadArtifactUrl?: string
555555
isJobView?: boolean
556556
isJobCI?: boolean
557557
type: HistoryComponentType

src/Shared/Helpers.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,3 +707,11 @@ export const decode = (data, isEncoded: boolean = false) =>
707707
agg[curr.key] = curr.value
708708
return agg
709709
}, {})
710+
711+
export const getFileNameFromHeaders = (headers: Headers) =>
712+
headers
713+
?.get('content-disposition')
714+
?.split(';')
715+
?.find((n) => n.includes('filename='))
716+
?.replace('filename=', '')
717+
.trim()
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { showError } from '@Common/Helper'
2+
import { ToastBody } from '@Common/ToastBody'
3+
import { useState } from 'react'
4+
import { toast } from 'react-toastify'
5+
import { API_STATUS_CODES } from '@Common/Constants'
6+
import { ServerErrors } from '@Common/ServerError'
7+
import { getFileNameFromHeaders } from '@Shared/Helpers'
8+
import { getDownloadResponse } from './service'
9+
import { HandleDownloadProps } from './types'
10+
11+
const useDownload = () => {
12+
const [isDownloading, setIsDownloading] = useState<boolean>(false)
13+
14+
/**
15+
* @param downloadUrl - API url for downloading file
16+
* @param filterType - Show toast 'Preparing file for download'
17+
* @param fileName - fileName of the downloaded file
18+
* @param showSuccessfulToast - show toast on successful download
19+
* @param downloadSuccessToastContent - Content to show in toast on successful download
20+
*/
21+
const handleDownload = async ({
22+
downloadUrl,
23+
showFilePreparingToast = false,
24+
fileName,
25+
showSuccessfulToast = true,
26+
downloadSuccessToastContent = 'Downloaded Successfully',
27+
}: HandleDownloadProps): Promise<Error | ServerErrors> => {
28+
setIsDownloading(true)
29+
if (showFilePreparingToast) {
30+
toast.info(
31+
<ToastBody
32+
title="Preparing file for download"
33+
subtitle="File will be downloaded when it is available."
34+
/>,
35+
)
36+
}
37+
try {
38+
const response = await getDownloadResponse(downloadUrl)
39+
if (response.status === API_STATUS_CODES.OK) {
40+
const data = await (response as any).blob()
41+
42+
// Create a new URL object
43+
const blobUrl = URL.createObjectURL(data)
44+
45+
// Create a link element
46+
const a = document.createElement('a')
47+
a.href = blobUrl
48+
49+
a.download = fileName || getFileNameFromHeaders(response.headers) || 'file.tgz'
50+
51+
// Append the link element to the DOM
52+
document.body.appendChild(a)
53+
54+
// Programmatically click the link to start the download
55+
a.click()
56+
57+
// Clean up the URL object after the download is complete
58+
setTimeout(() => {
59+
URL.revokeObjectURL(blobUrl)
60+
document.body.removeChild(a)
61+
}, 0)
62+
63+
if (showSuccessfulToast) {
64+
toast.success(downloadSuccessToastContent)
65+
}
66+
} else if (response.status === API_STATUS_CODES.NO_CONTENT) {
67+
throw new Error('No content to download')
68+
} else {
69+
const jsonResponseError: ServerErrors = await response?.json()
70+
throw new ServerErrors(jsonResponseError)
71+
}
72+
} catch (error) {
73+
setIsDownloading(false)
74+
showError(error)
75+
return error
76+
}
77+
setIsDownloading(false)
78+
return null
79+
}
80+
81+
return { handleDownload, isDownloading }
82+
}
83+
84+
export default useDownload
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as useDownload } from './UseDownload'
2+
export * from './types'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Host } from '@Common/Constants'
2+
3+
export const getDownloadResponse = (downloadUrl: string) => fetch(`${Host}/${downloadUrl}`)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface HandleDownloadProps {
2+
downloadUrl: string
3+
showFilePreparingToast?: boolean
4+
fileName?: string
5+
showSuccessfulToast?: boolean
6+
downloadSuccessToastContent?: string
7+
}

src/Shared/Hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
*/
1616

1717
export * from './UsePrompt'
18+
export * from './UseDownload'

0 commit comments

Comments
 (0)