From 6cda6c98041dde5e78e71636a82157105fa2a5bc Mon Sep 17 00:00:00 2001 From: Saloni Shah Date: Wed, 12 Nov 2025 09:57:07 -0500 Subject: [PATCH 1/3] use outputExpirationDate --- src/libs/ajax/teaspoons/teaspoons-models.ts | 1 + .../pipelines/utils/mock-utils.ts | 2 ++ .../pipelines/views/JobHistory.test.tsx | 23 ++++++++++++++++--- .../pipelines/views/JobHistory.tsx | 21 +++++------------ 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/libs/ajax/teaspoons/teaspoons-models.ts b/src/libs/ajax/teaspoons/teaspoons-models.ts index 122165c750..86be28b574 100644 --- a/src/libs/ajax/teaspoons/teaspoons-models.ts +++ b/src/libs/ajax/teaspoons/teaspoons-models.ts @@ -66,6 +66,7 @@ export interface PipelineRun { timeSubmitted: string; timeCompleted?: string; quotaConsumed?: number; + outputExpirationDate?: string; } export interface GetPipelineRunsResponse { diff --git a/src/pages/scientificServices/pipelines/utils/mock-utils.ts b/src/pages/scientificServices/pipelines/utils/mock-utils.ts index 41ddc07144..f914e49a66 100644 --- a/src/pages/scientificServices/pipelines/utils/mock-utils.ts +++ b/src/pages/scientificServices/pipelines/utils/mock-utils.ts @@ -98,5 +98,7 @@ export function mockPipelineRun(status: PipelineRunStatus): PipelineRun { timeSubmitted: '2023-10-01T00:00:00Z', timeCompleted: status === 'SUCCEEDED' || status === 'FAILED' ? new Date().toISOString() : undefined, quotaConsumed: status === 'SUCCEEDED' ? 500 : undefined, + outputExpirationDate: + status === 'SUCCEEDED' ? new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString() : undefined, // job outputs expire in 14 days }; } diff --git a/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx b/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx index 03bf42b61f..0719c39657 100644 --- a/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx +++ b/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor } from '@testing-library/react'; +import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { Teaspoons, TeaspoonsContract } from 'src/libs/ajax/teaspoons/Teaspoons'; @@ -174,6 +174,7 @@ describe('job history table', () => { ...mockPipelineRun('SUCCEEDED'), timeSubmitted: fifteenDaysAgo, timeCompleted: fifteenDaysAgo, + outputExpirationDate: new Date(Date.now()).toISOString(), }; const pipelineRuns = [pipelineRun]; @@ -195,9 +196,21 @@ describe('job history table', () => { expect(screen.getAllByText(pipelineRun.jobId)).toHaveLength(2); }); - const viewOutputsButton = screen.getByText('View Outputs'); + const table = screen.getByRole('table'); + const rows = within(table).getAllByRole('row'); + const dataRow = within(rows[1]).getAllByRole('cell'); + + const viewOutputsButton = within(dataRow[7]).getByText('View Outputs'); expect(viewOutputsButton).toBeInTheDocument(); expect(viewOutputsButton).toBeDisabled(); + + const user = userEvent.setup(); + await user.hover(viewOutputsButton); + + const tooltip = await within(dataRow[7]).findByText( + 'The outputs for this job have been deleted. Outputs are available for 14 days after job completion.' + ); + expect(tooltip).toBeInTheDocument(); }); it('shows View Error button for FAILED jobs', async () => { @@ -462,6 +475,7 @@ describe('job history table', () => { const pipelineRun = { ...mockPipelineRun('SUCCEEDED'), timeCompleted: '2023-10-15T14:00:00Z', + outputExpirationDate: '2023-10-29T14:00:00Z', }; const pipelineRuns = [pipelineRun]; @@ -483,7 +497,10 @@ describe('job history table', () => { expect(screen.getAllByText(pipelineRun.jobId)).toHaveLength(2); }); - expect(screen.getByText('Oct 15, 2023')).toBeInTheDocument(); + const table = screen.getByRole('table'); + const rows = within(table).getAllByRole('row'); + const cells = within(rows[1]).getAllByRole('cell'); + expect(cells[5]).toHaveTextContent('Oct 29, 2023'); }); }); }); diff --git a/src/pages/scientificServices/pipelines/views/JobHistory.tsx b/src/pages/scientificServices/pipelines/views/JobHistory.tsx index fde6d2ac33..4ffcda4f8f 100644 --- a/src/pages/scientificServices/pipelines/views/JobHistory.tsx +++ b/src/pages/scientificServices/pipelines/views/JobHistory.tsx @@ -284,13 +284,11 @@ const CompletedCell = ({ pipelineRun }: CellProps): ReactNode => { }; const DataDeletionDateCell = ({ pipelineRun }: CellProps): ReactNode => { - if (!pipelineRun.timeCompleted || !(pipelineRun.status === 'SUCCEEDED')) { + if (!pipelineRun.outputExpirationDate || !(pipelineRun.status === 'SUCCEEDED')) { return
N/A
; } - const completionDate = new Date(pipelineRun?.timeCompleted); - const deletionDate = new Date(completionDate); - deletionDate.setDate(deletionDate.getDate() + TEASPOONS_FILE_OUTPUT_TTL_DAYS); + const deletionDate = new Date(pipelineRun?.outputExpirationDate); const today = new Date(); const threeDaysFromNow = new Date(); @@ -339,9 +337,11 @@ const ActionCell = ({ pipelineRun }: CellProps): ReactNode => { return ; }); - const jobOutputsDeleted = + const jobOutputsDeleted = !!( pipelineRun.status === 'SUCCEEDED' && - (hoursElapsedSinceCompletion(pipelineRun) ?? -1) > 24 * TEASPOONS_FILE_OUTPUT_TTL_DAYS; // 24 hours * 14 days + pipelineRun.outputExpirationDate && + new Date() >= new Date(pipelineRun.outputExpirationDate) + ); return (
@@ -505,12 +505,3 @@ const hoursElapsedSinceSubmission = (pipelineRun: PipelineRun): number => { const currentTime = new Date(); return (currentTime.getTime() - submittedTime.getTime()) / (1000 * 60 * 60); }; - -const hoursElapsedSinceCompletion = (pipelineRun: PipelineRun): number | undefined => { - if (!pipelineRun.timeCompleted) { - return undefined; - } - const completedTime = new Date(pipelineRun.timeCompleted); - const currentTime = new Date(); - return (currentTime.getTime() - completedTime.getTime()) / (1000 * 60 * 60); -}; From b99e389e4084c73c0914292cf47286e1907011e3 Mon Sep 17 00:00:00 2001 From: Saloni Shah Date: Wed, 12 Nov 2025 12:31:00 -0500 Subject: [PATCH 2/3] add test --- .../pipelines/views/JobHistory.test.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx b/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx index 0719c39657..9b0b28eb43 100644 --- a/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx +++ b/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx @@ -1,3 +1,4 @@ +import { formatDatetime } from '@terra-ui-packages/core-utils'; import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; @@ -502,5 +503,44 @@ describe('job history table', () => { const cells = within(rows[1]).getAllByRole('cell'); expect(cells[5]).toHaveTextContent('Oct 29, 2023'); }); + + it('displays the deletion date in red for job outputs expiring in 2 days', async () => { + const twoDaysFromNow = new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(); + const twelveDaysAgo = new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toISOString(); + const pipelineRun = { + ...mockPipelineRun('SUCCEEDED'), + timeCompleted: twelveDaysAgo, + outputExpirationDate: twoDaysFromNow, + }; + const pipelineRuns = [pipelineRun]; + + const mockPipelineRunResponse = { + pageToken: null, + results: pipelineRuns, + totalResults: 1, + }; + + asMockedFn(Teaspoons).mockReturnValue( + partial({ + getAllPipelineRuns: jest.fn().mockReturnValue(mockPipelineRunResponse), + }) + ); + + render(); + + await waitFor(() => { + expect(screen.getAllByText(pipelineRun.jobId)).toHaveLength(2); + }); + + const table = screen.getByRole('table'); + const rows = within(table).getAllByRole('row'); + const cells = within(rows[1]).getAllByRole('cell'); + expect(cells[5]).toHaveTextContent(formatDatetime(twoDaysFromNow)); + + // assert the date text is in red + const deletionDateDiv = within(cells[5]).getByText(formatDatetime(twoDaysFromNow)).parentElement!; + const style = window.getComputedStyle(deletionDateDiv); + expect(style.color).toBe('rgb(219, 50, 20)'); + }); }); }); From 4919d5de0d44d50acea07ebff0b5a1cf6a8bf249 Mon Sep 17 00:00:00 2001 From: Saloni Shah Date: Wed, 12 Nov 2025 12:33:12 -0500 Subject: [PATCH 3/3] add comment --- .../scientificServices/pipelines/views/JobHistory.test.tsx | 2 +- src/pages/scientificServices/pipelines/views/JobHistory.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx b/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx index 9b0b28eb43..c5915d0f5f 100644 --- a/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx +++ b/src/pages/scientificServices/pipelines/views/JobHistory.test.tsx @@ -504,7 +504,7 @@ describe('job history table', () => { expect(cells[5]).toHaveTextContent('Oct 29, 2023'); }); - it('displays the deletion date in red for job outputs expiring in 2 days', async () => { + it('displays the deletion date in red for job outputs expiring within 3 days', async () => { const twoDaysFromNow = new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(); const twelveDaysAgo = new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toISOString(); const pipelineRun = { diff --git a/src/pages/scientificServices/pipelines/views/JobHistory.tsx b/src/pages/scientificServices/pipelines/views/JobHistory.tsx index 4ffcda4f8f..f2d9941217 100644 --- a/src/pages/scientificServices/pipelines/views/JobHistory.tsx +++ b/src/pages/scientificServices/pipelines/views/JobHistory.tsx @@ -337,6 +337,7 @@ const ActionCell = ({ pipelineRun }: CellProps): ReactNode => { return ; }); + // `!!` converts the expression to a boolean const jobOutputsDeleted = !!( pipelineRun.status === 'SUCCEEDED' && pipelineRun.outputExpirationDate &&