From 165e193842a698d2bdbdf6482a01be853e3e8663 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 17 Nov 2025 18:28:27 +0530 Subject: [PATCH 1/5] Scorecards attachments ui --- .../ScorecardAttachments.module.scss | 34 +++++ .../ScorecardAttachments.tsx | 138 +++++++++++++++++- .../src/lib/components/common/columnUtils.ts | 25 ++++ src/apps/review/src/lib/constants.ts | 1 + .../src/lib/hooks/useFetchAiWorkflowRuns.ts | 113 +++++++++++++- 5 files changed, 303 insertions(+), 8 deletions(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss index e69de29bb..450f8f115 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss @@ -0,0 +1,34 @@ +@import '@libs/ui/styles/includes'; + +.tableCell { + vertical-align: middle; +} + +.filenameCell { + display: flex; + align-items: center; + gap: 6px; + color: $link-blue-dark; + cursor: pointer; + opacity: 1; + transition: opacity 0.15s ease; + + &:hover { + text-decoration: underline; + } +} + +.downloading { + cursor: wait; + opacity: 0.6; +} + +.expired { + cursor: not-allowed; + opacity: 0.4; + color: #999; + + &:hover { + text-decoration: none; + } +} \ No newline at end of file diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx index a0091861c..e21937bd8 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx @@ -1,15 +1,139 @@ -import { FC } from 'react' +import { FC, useCallback, useMemo } from 'react' +import { noop } from 'lodash' +import classNames from 'classnames' +import moment from 'moment' -// import styles from './ScorecardAttachments.module.scss' +import { IconOutline, Table, TableColumn } from '~/libs/ui' +import { useAiScorecardContext } from '~/apps/review/src/pages/ai-scorecards/AiScorecardContext' + +import { AiScorecardContextModel } from '../../../models' +import { AiWorkflowRunArtifact, + AiWorkflowRunArtifactDownloadResponse, + AiWorkflowRunAttachmentsResponse, + useDownloadAiWorkflowsRunArtifact, useFetchAiWorkflowsRunAttachments } from '../../../hooks' +import { TableWrapper } from '../../TableWrapper' +import { TABLE_DATE_FORMAT } from '../../../constants' +import { formatFileSize } from '../../common' + +import styles from './ScorecardAttachments.module.scss' interface ScorecardAttachmentsProps { className?: string } -const ScorecardAttachments: FC = props => ( -
- attachments -
-) +const ScorecardAttachments: FC = (props: ScorecardAttachmentsProps) => { + const className = props.className + // const { width: screenWidth }: WindowSize = useWindowSize() + // const isTablet = useMemo(() => screenWidth <= 1000, [screenWidth]) + const { workflowId, workflowRun }: AiScorecardContextModel = useAiScorecardContext() + const { artifacts }: AiWorkflowRunAttachmentsResponse + = useFetchAiWorkflowsRunAttachments(workflowId, workflowRun?.id) + const { download, isDownloading }: AiWorkflowRunArtifactDownloadResponse = useDownloadAiWorkflowsRunArtifact( + workflowId, + workflowRun?.id, + ) + + const handleDownload = useCallback( + async (artifactId: number): Promise => { + await download(artifactId) + }, + [download], + ) + + const createDownloadHandler = useCallback( + (id: number) => () => handleDownload(id), + [handleDownload], + ) + + console.log('attachments', artifacts) + + const columns = useMemo[]>( + () => [ + { + className: classNames(styles.tableCell), + label: 'Filename', + propertyName: 'name', + renderer: (attachment: AiWorkflowRunArtifact) => { + const isExpired = attachment.expired + + return ( +
+ {attachment.name} + {isExpired && Expired} +
+ ) + }, + type: 'element', + }, + { + className: classNames(styles.tableCell), + label: 'Type', + renderer: () => ( +
+ + Artifact +
+ ), + type: 'element', + }, + { + className: classNames(styles.tableCell), + label: 'Size', + propertyName: 'sizeInBytes', + renderer: (attachment: AiWorkflowRunArtifact) => ( +
{formatFileSize(attachment.size_in_bytes)}
+ ), + type: 'element', + }, + { + className: styles.tableCell, + label: 'Attached Date', + renderer: (attachment: AiWorkflowRunArtifact) => ( + + {moment(attachment.created_at) + .local() + .format(TABLE_DATE_FORMAT)} + + ), + type: 'element', + }, + ], + [], + ) + + return ( + + {artifacts ? ( + + ) : ( +
No attachments
+ )} + + + ) + +} export default ScorecardAttachments diff --git a/src/apps/review/src/lib/components/common/columnUtils.ts b/src/apps/review/src/lib/components/common/columnUtils.ts index 0bfceebcc..d50da47f6 100644 --- a/src/apps/review/src/lib/components/common/columnUtils.ts +++ b/src/apps/review/src/lib/components/common/columnUtils.ts @@ -69,3 +69,28 @@ export const getProfileUrl = (handle: string): string => { return `${normalizedBase}/${encodeURIComponent(handle)}` } + +/** + * converts size_in_bytes into KB / MB / GB with correct formatting. + */ +export const formatFileSize = (bytes: number): string => { + if (!bytes || bytes < 0) return '0 B' + + const KB = 1024 + const MB = KB * 1024 + const GB = MB * 1024 + + if (bytes >= GB) { + return `${(bytes / GB).toFixed(2)} GB` + } + + if (bytes >= MB) { + return `${(bytes / MB).toFixed(2)} MB` + } + + if (bytes >= KB) { + return `${(bytes / KB).toFixed(2)} KB` + } + + return `${bytes} B` +} diff --git a/src/apps/review/src/lib/constants.ts b/src/apps/review/src/lib/constants.ts index 89f4cbd37..b03395e68 100644 --- a/src/apps/review/src/lib/constants.ts +++ b/src/apps/review/src/lib/constants.ts @@ -1,2 +1,3 @@ export const SUBMISSION_TYPE_CONTEST = 'CONTEST_SUBMISSION' export const SUBMISSION_TYPE_CHECKPOINT = 'CHECKPOINT_SUBMISSION' +export const TABLE_DATE_FORMAT = 'MMM DD, HH:mm A' diff --git a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts index 3120c869a..05b529f24 100644 --- a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts +++ b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import useSWR, { SWRResponse } from 'swr' import { EnvironmentConfig } from '~/config' @@ -46,6 +46,23 @@ export interface AiWorkflowRun { workflow: AiWorkflow } +export interface AiWorkflowRunArtifact { + id: number + name: string + size_in_bytes: number + url: string + archive_download_url: string + expired: boolean + workflow_run: { + id: number + repository_id: number + head_sha: string + } + created_at: string + updated_at: string + expires_at: string +} + export type AiWorkflowRunItem = AiFeedbackItem const TC_API_BASE_URL = EnvironmentConfig.API.V6 @@ -60,6 +77,22 @@ export interface AiWorkflowRunItemsResponse { isLoading: boolean } +export interface AiWorkflowRunAttachmentsApiResponse { + artifacts: AiWorkflowRunArtifact[] + total_count: number +} + +export interface AiWorkflowRunAttachmentsResponse { + artifacts: AiWorkflowRunArtifact[] + totalCount: number + isLoading: boolean +} + +export interface AiWorkflowRunArtifactDownloadResponse { + download: (artifactId: number) => Promise + isDownloading: boolean +} + export const aiRunInProgress = (aiRun: Pick): boolean => [ AiWorkflowRunStatusEnum.INIT, AiWorkflowRunStatusEnum.QUEUED, @@ -139,3 +172,81 @@ export function useFetchAiWorkflowsRunItems( runItems, } } + +export function useFetchAiWorkflowsRunAttachments( + workflowId: string, + runId: string | undefined, +): AiWorkflowRunAttachmentsResponse { + const { + data, + error: fetchError, + isValidating: isLoading, + }: SWRResponse = useSWR< + AiWorkflowRunAttachmentsApiResponse, + Error + >( + `${TC_API_BASE_URL}/workflows/${workflowId}/runs/${runId}/attachments`, + { + isPaused: () => !workflowId || !runId, + }, + ) + + useEffect(() => { + if (fetchError) { + handleError(fetchError) + } + }, [fetchError]) + + return { + artifacts: data?.artifacts ?? [], + isLoading, + totalCount: data?.total_count ?? 0, + } +} + +export function useDownloadAiWorkflowsRunArtifact( + workflowId: string, + runId: string | undefined, +): AiWorkflowRunArtifactDownloadResponse { + const [isDownloading, setIsDownloading] = useState(false) + + const download = async (artifactId: number): Promise => { + if (!workflowId || !runId || !artifactId) return + + try { + setIsDownloading(true) + + const url = `${TC_API_BASE_URL}/workflows/${workflowId}/runs/${runId}/attachments/${artifactId}/zip` + + const response = await fetch(url, { + credentials: 'include', + method: 'GET', + }) + + if (!response.ok) { + throw new Error(`Download failed with status ${response.status}`) + } + + const blob = await response.blob() + + // Create a blob URL and trigger browser download + const objectUrl = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = objectUrl + link.download = `artifact-${artifactId}.zip` + link.click() + + // Cleanup + window.URL.revokeObjectURL(objectUrl) + } catch (err) { + handleError(err as Error) + } finally { + setIsDownloading(false) + } + } + + return { + download, + isDownloading, + } +} From 2d9be6f58ce3ad61de734e77754e71f72a67f978 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 17 Nov 2025 18:58:55 +0530 Subject: [PATCH 2/5] Fix updated context --- .../ScorecardAttachments/ScorecardAttachments.tsx | 6 +++--- src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx index e21937bd8..5b4aabfd6 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx @@ -4,9 +4,8 @@ import classNames from 'classnames' import moment from 'moment' import { IconOutline, Table, TableColumn } from '~/libs/ui' -import { useAiScorecardContext } from '~/apps/review/src/pages/ai-scorecards/AiScorecardContext' +import { useReviewsContext } from '~/apps/review/src/pages/reviews/ReviewsContext' -import { AiScorecardContextModel } from '../../../models' import { AiWorkflowRunArtifact, AiWorkflowRunArtifactDownloadResponse, AiWorkflowRunAttachmentsResponse, @@ -14,6 +13,7 @@ import { AiWorkflowRunArtifact, import { TableWrapper } from '../../TableWrapper' import { TABLE_DATE_FORMAT } from '../../../constants' import { formatFileSize } from '../../common' +import { ReviewsContextModel } from '../../../models' import styles from './ScorecardAttachments.module.scss' @@ -25,7 +25,7 @@ const ScorecardAttachments: FC = (props: ScorecardAtt const className = props.className // const { width: screenWidth }: WindowSize = useWindowSize() // const isTablet = useMemo(() => screenWidth <= 1000, [screenWidth]) - const { workflowId, workflowRun }: AiScorecardContextModel = useAiScorecardContext() + const { workflowId, workflowRun }: ReviewsContextModel = useReviewsContext() const { artifacts }: AiWorkflowRunAttachmentsResponse = useFetchAiWorkflowsRunAttachments(workflowId, workflowRun?.id) const { download, isDownloading }: AiWorkflowRunArtifactDownloadResponse = useDownloadAiWorkflowsRunArtifact( diff --git a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts index 2b33d3bbe..706a7b961 100644 --- a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts +++ b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts @@ -172,8 +172,8 @@ export function useFetchAiWorkflowsRunItems( } export function useFetchAiWorkflowsRunAttachments( - workflowId: string, - runId: string | undefined, + workflowId?: string, + runId?: string | undefined, ): AiWorkflowRunAttachmentsResponse { const { data, @@ -203,8 +203,8 @@ export function useFetchAiWorkflowsRunAttachments( } export function useDownloadAiWorkflowsRunArtifact( - workflowId: string, - runId: string | undefined, + workflowId?: string, + runId?: string | undefined, ): AiWorkflowRunArtifactDownloadResponse { const [isDownloading, setIsDownloading] = useState(false) From 9b29f7d44cca98ce1ea623e5306940dc4ed4493c Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 17 Nov 2025 20:07:26 +0530 Subject: [PATCH 3/5] PM-2179 update logic --- .../ScorecardAttachments.module.scss | 11 +++++- .../ScorecardAttachments.tsx | 6 +-- .../src/lib/hooks/useFetchAiWorkflowRuns.ts | 38 +++++++++---------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss index 450f8f115..559bee5d9 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss @@ -25,10 +25,19 @@ .expired { cursor: not-allowed; - opacity: 0.4; color: #999; &:hover { text-decoration: none; } +} + +.artifactType { + display: flex; + gap: 5px; + align-items: center; + + .artifactIcon { + stroke: #00797A; + } } \ No newline at end of file diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx index 5b4aabfd6..c1f129da5 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx @@ -45,8 +45,6 @@ const ScorecardAttachments: FC = (props: ScorecardAtt [handleDownload], ) - console.log('attachments', artifacts) - const columns = useMemo[]>( () => [ { @@ -68,7 +66,7 @@ const ScorecardAttachments: FC = (props: ScorecardAtt onClick={!isExpired ? createDownloadHandler(attachment.id) : undefined} > {attachment.name} - {isExpired && Expired} + {isExpired && (Link Expired)} ) }, @@ -79,7 +77,7 @@ const ScorecardAttachments: FC = (props: ScorecardAtt label: 'Type', renderer: () => (
- + Artifact
), diff --git a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts index 706a7b961..db90c738a 100644 --- a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts +++ b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import useSWR, { SWRResponse } from 'swr' import { EnvironmentConfig } from '~/config' -import { xhrGetAsync } from '~/libs/core' +import { xhrGetAsync, xhrGetBlobAsync } from '~/libs/core' import { handleError } from '~/libs/shared/lib/utils/handle-error' import { AiFeedbackItem, Scorecard } from '../models' @@ -216,26 +216,22 @@ export function useDownloadAiWorkflowsRunArtifact( const url = `${TC_API_BASE_URL}/workflows/${workflowId}/runs/${runId}/attachments/${artifactId}/zip` - const response = await fetch(url, { - credentials: 'include', - method: 'GET', - }) - - if (!response.ok) { - throw new Error(`Download failed with status ${response.status}`) - } - - const blob = await response.blob() - - // Create a blob URL and trigger browser download - const objectUrl = window.URL.createObjectURL(blob) - const link = document.createElement('a') - link.href = objectUrl - link.download = `artifact-${artifactId}.zip` - link.click() - - // Cleanup - window.URL.revokeObjectURL(objectUrl) + xhrGetBlobAsync(url) + .then(blob => { + const objectUrl = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = objectUrl + link.download = `artifact-${artifactId}.zip` + + document.body.appendChild(link) + link.click() + link.remove() + + window.URL.revokeObjectURL(objectUrl) + }) + .catch(err => { + handleError(err as Error) + }) } catch (err) { handleError(err as Error) } finally { From 6eb0aa6f039eb5beeba41b21c1a737d3c7eadf6a Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Mon, 17 Nov 2025 20:21:47 +0530 Subject: [PATCH 4/5] Add screen height --- .../ScorecardAttachments/ScorecardAttachments.module.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss index 559bee5d9..5a979bac5 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss @@ -1,5 +1,9 @@ @import '@libs/ui/styles/includes'; +.container { + min-height: calc($content-height - 250px); +} + .tableCell { vertical-align: middle; } From e4be6d9fa24f7c406bd1a13018ace556d2e16e13 Mon Sep 17 00:00:00 2001 From: himaniraghav3 Date: Tue, 18 Nov 2025 13:51:47 +0530 Subject: [PATCH 5/5] Add mobile view, fix issues from PR feedback --- .../ScorecardAttachments.module.scss | 44 +++++++++- .../ScorecardAttachments.tsx | 83 +++++++++++++++++-- .../src/lib/hooks/useFetchAiWorkflowRuns.ts | 54 ++++++------ .../AiReviewViewer/AiReviewViewer.tsx | 14 ++-- 4 files changed, 152 insertions(+), 43 deletions(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss index 5a979bac5..8eda551a0 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.module.scss @@ -1,7 +1,19 @@ @import '@libs/ui/styles/includes'; -.container { +.tableWrapper { min-height: calc($content-height - 250px); + font-size: 14px; + + &:global(.enhanced-table) { + table { + th, + td { + text-align: left; + background-color: white; + } + + } + } } .tableCell { @@ -44,4 +56,32 @@ .artifactIcon { stroke: #00797A; } -} \ No newline at end of file +} + +.noAttachmentText { + text-align: center; +} + +.mobileRow { + padding: 16px 8px; + border-bottom: 1px solid #A8A8A8; +} + +.mobileHeader { + display: flex; + gap: 12px; +} + +.mobileExpanded { + padding: 16px 20px 0px 32px; +} + +.rowItem { + display: flex; + justify-content: space-between; +} + +.rowItemHeading { + font-weight: 700; + color: #0a0a0a; +} diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx index c1f129da5..54b78f74e 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardAttachments/ScorecardAttachments.tsx @@ -1,10 +1,11 @@ -import { FC, useCallback, useMemo } from 'react' +import { FC, useCallback, useMemo, useState } from 'react' import { noop } from 'lodash' import classNames from 'classnames' import moment from 'moment' import { IconOutline, Table, TableColumn } from '~/libs/ui' import { useReviewsContext } from '~/apps/review/src/pages/reviews/ReviewsContext' +import { useWindowSize, WindowSize } from '~/libs/shared' import { AiWorkflowRunArtifact, AiWorkflowRunArtifactDownloadResponse, @@ -23,8 +24,8 @@ interface ScorecardAttachmentsProps { const ScorecardAttachments: FC = (props: ScorecardAttachmentsProps) => { const className = props.className - // const { width: screenWidth }: WindowSize = useWindowSize() - // const isTablet = useMemo(() => screenWidth <= 1000, [screenWidth]) + const { width: screenWidth }: WindowSize = useWindowSize() + const isTablet = useMemo(() => screenWidth <= 1000, [screenWidth]) const { workflowId, workflowRun }: ReviewsContextModel = useReviewsContext() const { artifacts }: AiWorkflowRunAttachmentsResponse = useFetchAiWorkflowsRunAttachments(workflowId, workflowRun?.id) @@ -105,28 +106,94 @@ const ScorecardAttachments: FC = (props: ScorecardAtt type: 'element', }, ], + [createDownloadHandler, isDownloading], + ) + + const [openRow, setOpenRow] = useState(undefined) + const toggleRow = useCallback( + (id: number) => () => { + setOpenRow(prev => (prev === id ? undefined : id)) + }, [], ) + const renderMobileRow = (attachment: AiWorkflowRunArtifact): JSX.Element => { + const isExpired = attachment.expired + const downloading = isDownloading + const isOpen = openRow === attachment.id + + return ( +
+ {/* Top collapsed row */} +
+ +
+ {attachment.name} +
+ +
+ + {/* Expanded content */} + {isOpen && ( +
+
+ Type: +
+ + Artifact +
+
+ +
+ Size: + {formatFileSize(attachment.size_in_bytes)} +
+ +
+ Date: + {moment(attachment.created_at) + .local() + .format(TABLE_DATE_FORMAT)} +
+
+ )} +
+ ) + } return ( - {artifacts ? ( + {!artifacts || artifacts.length === 0 ? ( +
No attachments
+ ) : isTablet ? ( +
+ {artifacts.map(renderMobileRow)} +
+ ) : (
- ) : ( -
No attachments
)} diff --git a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts index db90c738a..c7afa253d 100644 --- a/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts +++ b/src/apps/review/src/lib/hooks/useFetchAiWorkflowRuns.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import useSWR, { SWRResponse } from 'swr' import { EnvironmentConfig } from '~/config' @@ -204,40 +204,38 @@ export function useFetchAiWorkflowsRunAttachments( export function useDownloadAiWorkflowsRunArtifact( workflowId?: string, - runId?: string | undefined, + runId?: string, ): AiWorkflowRunArtifactDownloadResponse { const [isDownloading, setIsDownloading] = useState(false) - const download = async (artifactId: number): Promise => { - if (!workflowId || !runId || !artifactId) return + const download = useCallback( + async (artifactId: number): Promise => { + if (!workflowId || !runId || !artifactId) return - try { setIsDownloading(true) - const url = `${TC_API_BASE_URL}/workflows/${workflowId}/runs/${runId}/attachments/${artifactId}/zip` - xhrGetBlobAsync(url) - .then(blob => { - const objectUrl = window.URL.createObjectURL(blob) - const link = document.createElement('a') - link.href = objectUrl - link.download = `artifact-${artifactId}.zip` - - document.body.appendChild(link) - link.click() - link.remove() - - window.URL.revokeObjectURL(objectUrl) - }) - .catch(err => { - handleError(err as Error) - }) - } catch (err) { - handleError(err as Error) - } finally { - setIsDownloading(false) - } - } + try { + const blob = await xhrGetBlobAsync(url) + + const objectUrl = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = objectUrl + link.download = `artifact-${artifactId}.zip` + + document.body.appendChild(link) + link.click() + link.remove() + + window.URL.revokeObjectURL(objectUrl) + } catch (err) { + handleError(err as Error) + } finally { + setIsDownloading(false) + } + }, + [workflowId, runId], + ) return { download, diff --git a/src/apps/review/src/pages/reviews/components/AiReviewViewer/AiReviewViewer.tsx b/src/apps/review/src/pages/reviews/components/AiReviewViewer/AiReviewViewer.tsx index ca1f14802..fe94c883d 100644 --- a/src/apps/review/src/pages/reviews/components/AiReviewViewer/AiReviewViewer.tsx +++ b/src/apps/review/src/pages/reviews/components/AiReviewViewer/AiReviewViewer.tsx @@ -4,8 +4,10 @@ import { Tabs } from '~/apps/review/src/lib' import { ScorecardViewer } from '~/apps/review/src/lib/components/Scorecard' import { ScorecardAttachments } from '~/apps/review/src/lib/components/Scorecard/ScorecardAttachments' import { + AiWorkflowRunAttachmentsResponse, AiWorkflowRunItemsResponse, AiWorkflowRunStatusEnum, + useFetchAiWorkflowsRunAttachments, useFetchAiWorkflowsRunItems, } from '~/apps/review/src/lib/hooks' import { ReviewsContextModel, SelectOption } from '~/apps/review/src/lib/models' @@ -15,15 +17,17 @@ import { useReviewsContext } from '../../ReviewsContext' import styles from './AiReviewViewer.module.scss' -const tabItems: SelectOption[] = [ - { label: 'Scorecard', value: 'scorecard' }, - { label: 'Attachments', value: 'attachments' }, -] - const AiReviewViewer: FC = () => { const { scorecard, workflowId, workflowRun }: ReviewsContextModel = useReviewsContext() const [selectedTab, setSelectedTab] = useState('scorecard') const { runItems }: AiWorkflowRunItemsResponse = useFetchAiWorkflowsRunItems(workflowId, workflowRun?.id) + const { totalCount }: AiWorkflowRunAttachmentsResponse + = useFetchAiWorkflowsRunAttachments(workflowId, workflowRun?.id) + + const tabItems: SelectOption[] = [ + { label: 'Scorecard', value: 'scorecard' }, + { label: `Attachments (${totalCount ?? 0})`, value: 'attachments' }, + ] const isFailedRun = useMemo(() => ( workflowRun && [ AiWorkflowRunStatusEnum.CANCELLED,