From e68bf598c368a3eaa69f3fabad93ac12a28e2b2a Mon Sep 17 00:00:00 2001 From: ncdiehl11 Date: Fri, 26 Jun 2026 09:38:00 -0400 Subject: [PATCH 1/6] feat(app): create DeleteRecordsModal This PR builds a flexible DeleteRecordsModal component for confirming deleting batches of run records or compliance log periods. This modal is triggered in a few places- 1) from the Recent Protocols Run tab of a device's details ('allRuns' variant), 2) from the Compliance Ready Softare files section of a robot's file manager settings page (for compliance ready robots only, 'allLogs' variant), and 3) from the Prtoocol Run Records section of a robot's file manager settings page ('selectedRuns' variant). Closes EXEC-2801 --- .../localization/en/device_details.json | 10 ++++ .../__tests__/DeleteRecordsModal.test.tsx | 50 +++++++++++++++++++ .../deleterecordsmodal.module.css | 19 +++++++ .../hooks/useDeleteRecordsText.ts | 27 ++++++++++ .../Devices/DeleteRecordsModal/index.tsx | 48 ++++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 app/src/organisms/Desktop/Devices/DeleteRecordsModal/__tests__/DeleteRecordsModal.test.tsx create mode 100644 app/src/organisms/Desktop/Devices/DeleteRecordsModal/deleterecordsmodal.module.css create mode 100644 app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts create mode 100644 app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx diff --git a/app/src/assets/localization/en/device_details.json b/app/src/assets/localization/en/device_details.json index 7bf132d898a..f84554282ca 100644 --- a/app/src/assets/localization/en/device_details.json +++ b/app/src/assets/localization/en/device_details.json @@ -57,7 +57,17 @@ "deck_fixture_setup_modal_top_description": "First, unscrew and remove the deck slot where you'll install a fixture. Then put the fixture in place and attach it as needed.", "deck_hardware": "deck hardware", "deck_slot": "deck slot {{slot}}", + "delete_all_logs": "Delete all logs?", + "delete_all_logs_description": "Deleting all logs will permanently remove them from the robot. This action cannot be undone.", + "delete_all_logs_recommendation": "We recommend downloading all log files before proceeding.", + "delete_all_run_records": "Delete all protocol run records?", + "delete_all_run_records_description": "Deleting all protocol run records will permanently remove them from the robot, along with all associated files. This action cannot be undone.", + "delete_all_run_records_recommendation": "We recommend downloading all protocol files before proceeding.", "delete_run": "Delete protocol run record", + "delete_selected": "Delete selected", + "delete_selected_run_records": "Delete selected protocol run records?", + "delete_selected_run_records_description": "Deleting the selected protocol run records will permanently remove them from the robot, along with all associated files. This action cannot be undone.", + "delete_selected_run_records_recommendation": "We recommend downloading all protocol files before proceeding.", "detach_gripper": "Detach gripper", "detach_pipette": "Detach pipette", "disable_camera": "Disable camera", diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/__tests__/DeleteRecordsModal.test.tsx b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/__tests__/DeleteRecordsModal.test.tsx new file mode 100644 index 00000000000..1ac646b5aba --- /dev/null +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/__tests__/DeleteRecordsModal.test.tsx @@ -0,0 +1,50 @@ +import { screen } from '@testing-library/react' +import { describe, it, vi } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' + +import { DeleteRecordsModal } from '..' + +const render = ( + type: React.ComponentProps['type'] +) => + renderWithProviders( + , + { i18nInstance: i18n } + ) + +describe('DeleteRecordsModal', () => { + it('renders allRuns copy', () => { + render('allRuns') + screen.getByText('Delete all protocol run records?') + screen.getByText( + 'Deleting all protocol run records will permanently remove them from the robot, along with all associated files. This action cannot be undone.' + ) + screen.getByText( + 'We recommend downloading all protocol files before proceeding.' + ) + }) + + it('renders selectedRuns copy', () => { + render('selectedRuns') + screen.getByText('Delete selected protocol run records?') + screen.getByText( + 'Deleting the selected protocol run records will permanently remove them from the robot, along with all associated files. This action cannot be undone.' + ) + screen.getByText( + 'We recommend downloading all protocol files before proceeding.' + ) + }) + + it('renders allLogs copy', () => { + render('allLogs') + screen.getByText('Delete all logs?') + screen.getByText( + 'Deleting all logs will permanently remove them from the robot. This action cannot be undone.' + ) + screen.getByText( + 'We recommend downloading all log files before proceeding.' + ) + }) +}) diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/deleterecordsmodal.module.css b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/deleterecordsmodal.module.css new file mode 100644 index 00000000000..373be325d02 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/deleterecordsmodal.module.css @@ -0,0 +1,19 @@ +.button_row { + display: flex; + height: 100%; + align-items: center; + justify-content: flex-end; + gap: var(--spacing-8); +} + +.description { + display: flex; + flex-direction: column; + gap: var(--spacing-8); +} + +.modal_content { + display: flex; + flex-direction: column; + gap: var(--spacing-24); +} diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts new file mode 100644 index 00000000000..02d537b0c6b --- /dev/null +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts @@ -0,0 +1,27 @@ +import { useTranslation } from 'react-i18next' + +export function useDeleteRecordsText( + type: 'allRuns' | 'selectedRuns' | 'allLogs' +): { title: string; description: string; recommendation: string } { + const { t } = useTranslation('device_details') + switch (type) { + case 'allRuns': + return { + title: t('delete_all_run_records'), + description: t('delete_all_run_records_description'), + recommendation: t('delete_all_run_records_recommendation'), + } + case 'selectedRuns': + return { + title: t('delete_selected_run_records'), + description: t('delete_selected_run_records_description'), + recommendation: t('delete_selected_run_records_recommendation'), + } + case 'allLogs': + return { + title: t('delete_all_logs'), + description: t('delete_all_logs_description'), + recommendation: t('delete_all_logs_recommendation'), + } + } +} diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx new file mode 100644 index 00000000000..26328564663 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx @@ -0,0 +1,48 @@ +import { useTranslation } from 'react-i18next' + +import { + AlertPrimaryButton, + Modal, + SecondaryButton, + StyledText, +} from '@opentrons/components' + +import styles from './deleterecordsmodal.module.css' +import { useDeleteRecordsText } from './hooks/useDeleteRecordsText' + +interface DeleteRecordsModalProps { + onClose: () => void + onConfirm: () => void + type: 'allRuns' | 'selectedRuns' | 'allLogs' +} + +export function DeleteRecordsModal( + props: DeleteRecordsModalProps +): JSX.Element { + const { onClose, onConfirm, type } = props + const { t } = useTranslation(['device_details', 'shared']) + const { title, description, recommendation } = useDeleteRecordsText(type) + + return ( + +
+
+ + {description} + + + {recommendation} + +
+
+ + {t('shared:cancel')} + + + {t('delete_all')} + +
+
+
+ ) +} From 13476a297e3e7c79f0b9b30471098f8ac2ab7c4a Mon Sep 17 00:00:00 2001 From: ncdiehl11 Date: Fri, 26 Jun 2026 09:58:38 -0400 Subject: [PATCH 2/6] refactor DeleteRecordsModal variant to defined type --- .../DeleteRecordsModal/hooks/useDeleteRecordsText.ts | 10 +++++++--- .../Desktop/Devices/DeleteRecordsModal/index.tsx | 4 +++- .../Desktop/Devices/DeleteRecordsModal/types.ts | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 app/src/organisms/Desktop/Devices/DeleteRecordsModal/types.ts diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts index 02d537b0c6b..f4afb966d96 100644 --- a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts @@ -1,8 +1,12 @@ import { useTranslation } from 'react-i18next' -export function useDeleteRecordsText( - type: 'allRuns' | 'selectedRuns' | 'allLogs' -): { title: string; description: string; recommendation: string } { +import type { DeleteRecordsType } from '../types' + +export function useDeleteRecordsText(type: DeleteRecordsType): { + title: string + description: string + recommendation: string +} { const { t } = useTranslation('device_details') switch (type) { case 'allRuns': diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx index 26328564663..f017bec689b 100644 --- a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx @@ -10,10 +10,12 @@ import { import styles from './deleterecordsmodal.module.css' import { useDeleteRecordsText } from './hooks/useDeleteRecordsText' +import type { DeleteRecordsType } from './types' + interface DeleteRecordsModalProps { onClose: () => void onConfirm: () => void - type: 'allRuns' | 'selectedRuns' | 'allLogs' + type: DeleteRecordsType } export function DeleteRecordsModal( diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/types.ts b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/types.ts new file mode 100644 index 00000000000..8898f3376ea --- /dev/null +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/types.ts @@ -0,0 +1 @@ +export type DeleteRecordsType = 'allRuns' | 'selectedRuns' | 'allLogs' From 2e715a1a02a3516a4334e4eec669044af3b4eb13 Mon Sep 17 00:00:00 2001 From: ncdiehl11 Date: Fri, 26 Jun 2026 10:16:49 -0400 Subject: [PATCH 3/6] wire up modal in RecentProtocolRuns for smoke testing --- .../Desktop/Devices/RecentProtocolRuns.tsx | 275 ++++++++++-------- 1 file changed, 152 insertions(+), 123 deletions(-) diff --git a/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx index c109587f344..56c6dd5b478 100644 --- a/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { @@ -28,6 +29,7 @@ import { } from '/app/resources/runs' import { RECENT_PROTOCOL_RUNS_HEADER } from './constants' +import { DeleteRecordsModal } from './DeleteRecordsModal' import { HistoricalProtocolRun } from './HistoricalProtocolRun' interface RecentProtocolRunsProps { @@ -44,145 +46,172 @@ export function RecentProtocolRuns({ const protocols = useAllProtocolsQuery() const currentRunId = useCurrentRunId() const { isRunTerminal } = useRunStatuses() + const [showDeleteRecordsModal, setShowDeleteRecordsModal] = + useState(false) + + // TODO: wire up delete runs handler + const handleConfirmDeleteRuns = (): void => { + setShowDeleteRecordsModal(false) + } + const robotIsBusy = currentRunId != null ? !isRunTerminal : false const allRunsMutable = [...(runs ?? [])] return ( - + <> + {showDeleteRecordsModal ? ( + { + setShowDeleteRecordsModal(false) + }} + onConfirm={handleConfirmDeleteRuns} + /> + ) : null} - - {t('run_history')} - - {}} - iconName="download" + + {t('run_history')} + + - {t('download_all')} - - {}}>{t('delete_all')} - - - - {isRobotViewable && allRunsMutable && allRunsMutable?.length > 0 && ( - <> - { + setShowDeleteRecordsModal(true) + }} + iconName="download" > - - {t('run_date')} - - - {t('protocol')} - - - {t('status')} - - + { + setShowDeleteRecordsModal(true) + }} + > + {t('delete_all')} + + + + + {isRobotViewable && allRunsMutable && allRunsMutable?.length > 0 && ( + <> + - {t('files')} - - + {t('run_date')} + + + {t('protocol')} + + + {t('status')} + + + {t('files')} + + + {t('run_duration')} + + + - {t('run_duration')} - - - - {allRunsMutable - .sort( - (a, b) => - new Date(b.createdAt).getTime() - - new Date(a.createdAt).getTime() - ) - - .map((run, index) => { - const protocol = protocols?.data?.data.find( - protocol => protocol.id === run.protocolId + {allRunsMutable + .sort( + (a, b) => + new Date(b.createdAt).getTime() - + new Date(a.createdAt).getTime() ) - const protocolName = - protocol?.metadata.protocolName ?? - protocol?.files[0].name ?? - t('shared:loading') ?? - '' - return ( - - ) - })} - - - )} - {!isRobotViewable && ( - - )} - {isRobotViewable && allRunsMutable?.length === 0 && ( - - {t('no_protocol_runs')} - - )} + .map((run, index) => { + const protocol = protocols?.data?.data.find( + protocol => protocol.id === run.protocolId + ) + const protocolName = + protocol?.metadata.protocolName ?? + protocol?.files[0].name ?? + t('shared:loading') ?? + '' + + return ( + + ) + })} + + + )} + {!isRobotViewable && ( + + )} + {isRobotViewable && allRunsMutable?.length === 0 && ( + + {t('no_protocol_runs')} + + )} + - + ) } From 5ff2c40c51579d73073e2b0d7fe51a319c7bd863 Mon Sep 17 00:00:00 2001 From: ncdiehl11 Date: Fri, 26 Jun 2026 12:50:49 -0400 Subject: [PATCH 4/6] refactor RecentProtocolRuns --- .../Devices/HistoricalProtocolRun.module.css | 5 - .../HistoricalProtocolRun.tsx | 8 +- .../HistoricalProtocolRunDrawer.tsx | 6 +- .../HistoricalProtocolRunOverflowMenu.tsx | 2 +- .../__tests__/HistoricalProtocolRun.test.tsx | 0 ...HistoricalProtocolRunOverflowMenu.test.tsx | 6 +- .../__tests__/RecentProtocolRuns.test.tsx | 2 +- .../index.tsx} | 101 ++++-------------- .../recentprotocolruns.module.css | 59 ++++++++++ .../RobotSettingsFileManagers.test.tsx | 23 ---- .../organisms/Desktop/Devices/constants.ts | 5 - 11 files changed, 92 insertions(+), 125 deletions(-) delete mode 100644 app/src/organisms/Desktop/Devices/HistoricalProtocolRun.module.css rename app/src/organisms/Desktop/Devices/{ => RecentProtocolRuns}/HistoricalProtocolRun.tsx (94%) rename app/src/organisms/Desktop/Devices/{ => RecentProtocolRuns}/HistoricalProtocolRunDrawer.tsx (98%) rename app/src/organisms/Desktop/Devices/{ => RecentProtocolRuns}/HistoricalProtocolRunOverflowMenu.tsx (99%) rename app/src/organisms/Desktop/Devices/{ => RecentProtocolRuns}/__tests__/HistoricalProtocolRun.test.tsx (100%) rename app/src/organisms/Desktop/Devices/{ => RecentProtocolRuns}/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx (97%) rename app/src/organisms/Desktop/Devices/{ => RecentProtocolRuns}/__tests__/RecentProtocolRuns.test.tsx (98%) rename app/src/organisms/Desktop/Devices/{RecentProtocolRuns.tsx => RecentProtocolRuns/index.tsx} (66%) create mode 100644 app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css delete mode 100644 app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsFileManager/__tests__/RobotSettingsFileManagers.test.tsx diff --git a/app/src/organisms/Desktop/Devices/HistoricalProtocolRun.module.css b/app/src/organisms/Desktop/Devices/HistoricalProtocolRun.module.css deleted file mode 100644 index ed04d468ce6..00000000000 --- a/app/src/organisms/Desktop/Devices/HistoricalProtocolRun.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.protocol_name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } diff --git a/app/src/organisms/Desktop/Devices/HistoricalProtocolRun.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRun.tsx similarity index 94% rename from app/src/organisms/Desktop/Devices/HistoricalProtocolRun.tsx rename to app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRun.tsx index c947407f342..650ba1a254c 100644 --- a/app/src/organisms/Desktop/Devices/HistoricalProtocolRun.tsx +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRun.tsx @@ -15,12 +15,14 @@ import { EMPTY_TIMESTAMP } from '/app/resources/runs' import { formatInterval } from '/app/transformations/commands' import { formatTimestamp } from '/app/transformations/runs' -import { RECENT_PROTOCOL_RUNS_HEADER } from './constants' -import styles from './HistoricalProtocolRun.module.css' import { HistoricalProtocolRunOverflowMenu as OverflowMenu } from './HistoricalProtocolRunOverflowMenu' +import styles from './recentprotocolruns.module.css' import type { RunData } from '@opentrons/api-client' +// inclusive of overflow menu button +const RECENT_PROTOCOL_RUNS_COLUMNS = '30% 25% 16% 5% 14% 10%' + interface HistoricalProtocolRunProps { run: RunData protocolName: string @@ -72,7 +74,7 @@ export function HistoricalProtocolRun( diff --git a/app/src/organisms/Desktop/Devices/HistoricalProtocolRunDrawer.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRunDrawer.tsx similarity index 98% rename from app/src/organisms/Desktop/Devices/HistoricalProtocolRunDrawer.tsx rename to app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRunDrawer.tsx index 472bc40809a..f0c0f2c25dc 100644 --- a/app/src/organisms/Desktop/Devices/HistoricalProtocolRunDrawer.tsx +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRunDrawer.tsx @@ -48,9 +48,9 @@ import { useIsFlex, useRobotType } from '/app/redux-resources/robots' import { useRunGeneratedDataFiles } from '/app/resources/dataFiles/useRunGeneratedDataFiles' import { useMostRecentCompletedAnalysis } from '/app/resources/runs' -import { OffsetTag } from '../../LabwarePositionCheck' -import { DownloadCsvFileLink } from './DownloadCsvFileLink' -import { useDeckCalibrationData } from './hooks' +import { OffsetTag } from '../../../LabwarePositionCheck' +import { DownloadCsvFileLink } from '../DownloadCsvFileLink' +import { useDeckCalibrationData } from '../hooks' import type { LabwareOffset, RunData } from '@opentrons/api-client' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' diff --git a/app/src/organisms/Desktop/Devices/HistoricalProtocolRunOverflowMenu.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRunOverflowMenu.tsx similarity index 99% rename from app/src/organisms/Desktop/Devices/HistoricalProtocolRunOverflowMenu.tsx rename to app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRunOverflowMenu.tsx index 4e760f6ec72..78ebe5e9929 100644 --- a/app/src/organisms/Desktop/Devices/HistoricalProtocolRunOverflowMenu.tsx +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/HistoricalProtocolRunOverflowMenu.tsx @@ -55,7 +55,7 @@ import { import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' import { useIsEstopNotDisengaged } from '/app/resources/devices' -import { useDownloadRunLog } from './hooks' +import { useDownloadRunLog } from '../hooks' import type { MouseEventHandler } from 'react' import type { Run } from '@opentrons/api-client' diff --git a/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRun.test.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/HistoricalProtocolRun.test.tsx similarity index 100% rename from app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRun.test.tsx rename to app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/HistoricalProtocolRun.test.tsx diff --git a/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx similarity index 97% rename from app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx rename to app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx index 866c9c84ba6..5f3afec730f 100644 --- a/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx @@ -26,9 +26,9 @@ import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' import { useIsEstopNotDisengaged } from '/app/resources/devices' import { useNotifyAllCommandsQuery } from '/app/resources/runs' +import { useDownloadRunLog } from '../../hooks' +import runRecord from '../../ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__fixtures__/runRecord.json' import { HistoricalProtocolRunOverflowMenu } from '../HistoricalProtocolRunOverflowMenu' -import { useDownloadRunLog } from '../hooks' -import runRecord from '../ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__fixtures__/runRecord.json' import type { ComponentProps } from 'react' import type { UseQueryResult } from 'react-query' @@ -37,7 +37,7 @@ import type { CommandsData } from '@opentrons/api-client' vi.mock('/app/redux/analytics') vi.mock('/app/redux/robot-update/selectors') vi.mock('/app/redux-resources/robots') -vi.mock('../hooks') +vi.mock('../../hooks') vi.mock('/app/organisms/RunTimeControl') vi.mock('/app/redux/analytics') vi.mock('/app/redux/config') diff --git a/app/src/organisms/Desktop/Devices/__tests__/RecentProtocolRuns.test.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/RecentProtocolRuns.test.tsx similarity index 98% rename from app/src/organisms/Desktop/Devices/__tests__/RecentProtocolRuns.test.tsx rename to app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/RecentProtocolRuns.test.tsx index 1e828514f65..f42b8d12323 100644 --- a/app/src/organisms/Desktop/Devices/__tests__/RecentProtocolRuns.test.tsx +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/__tests__/RecentProtocolRuns.test.tsx @@ -10,8 +10,8 @@ import { i18n } from '/app/i18n' import { useIsRobotViewable } from '/app/redux-resources/robots' import { useNotifyAllRunsQuery, useRunStatuses } from '/app/resources/runs' +import { RecentProtocolRuns } from '../../RecentProtocolRuns' import { HistoricalProtocolRun } from '../HistoricalProtocolRun' -import { RecentProtocolRuns } from '../RecentProtocolRuns' import type { AxiosError } from 'axios' import type { UseQueryResult } from 'react-query' diff --git a/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/index.tsx similarity index 66% rename from app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx rename to app/src/organisms/Desktop/Devices/RecentProtocolRuns/index.tsx index 56c6dd5b478..b49ed6cb739 100644 --- a/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/index.tsx @@ -1,24 +1,7 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - ALIGN_FLEX_START, - BasicButton, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DISPLAY_FLEX, - Flex, - InfoScreen, - JUSTIFY_FLEX_END, - JUSTIFY_FLEX_START, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - SIZE_4, - SPACING, - StyledText, -} from '@opentrons/components' +import { BasicButton, InfoScreen, StyledText } from '@opentrons/components' import { useAllProtocolsQuery } from '@opentrons/react-api-client' import { useIsRobotViewable } from '/app/redux-resources/robots' @@ -28,9 +11,9 @@ import { useRunStatuses, } from '/app/resources/runs' -import { RECENT_PROTOCOL_RUNS_HEADER } from './constants' -import { DeleteRecordsModal } from './DeleteRecordsModal' +import { DeleteRecordsModal } from '../DeleteRecordsModal' import { HistoricalProtocolRun } from './HistoricalProtocolRun' +import styles from './recentprotocolruns.module.css' interface RecentProtocolRunsProps { robotName: string @@ -55,7 +38,10 @@ export function RecentProtocolRuns({ } const robotIsBusy = currentRunId != null ? !isRunTerminal : false + + // TODO (nd, 06/25/2026): audit this once full run delete endpoint is created const allRunsMutable = [...(runs ?? [])] + return ( <> {showDeleteRecordsModal ? ( @@ -67,31 +53,12 @@ export function RecentProtocolRuns({ onConfirm={handleConfirmDeleteRuns} /> ) : null} - - - +
+
+ {t('run_history')} - +
{ @@ -108,27 +75,12 @@ export function RecentProtocolRuns({ > {t('delete_all')} - - - +
+
+
{isRobotViewable && allRunsMutable && allRunsMutable?.length > 0 && ( <> - +
{t('run_duration')} - - +
+
{allRunsMutable .sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ) - .map((run, index) => { const protocol = protocols?.data?.data.find( protocol => protocol.id === run.protocolId @@ -193,25 +140,17 @@ export function RecentProtocolRuns({ /> ) })} - +
)} {!isRobotViewable && ( )} {isRobotViewable && allRunsMutable?.length === 0 && ( - - {t('no_protocol_runs')} - + )} -
- +
+
) } diff --git a/app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css new file mode 100644 index 00000000000..938e3ab5a20 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css @@ -0,0 +1,59 @@ +.container { + display: flex; + width: 100%; + flex-direction: column; + align-items: flex-start; + padding: 0 0 6rem; + border-radius: 8px; + background-color: var(--white); +} + +.header { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + padding: 1rem; + border-bottom: 1px solid var(--grey-30); + gap: 0.5rem; +} + +.header_actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0.5rem; +} + +.content { + display: flex; + width: 100%; + min-height: 8rem; + flex-direction: column; + align-items: center; + padding: 1rem; +} + +.column_headers { + display: grid; + width: 88%; + justify-content: flex-start; + padding: 0.5rem; + margin-right: 12%; + color: var(--grey-60); + gap: 1.25rem; + grid-template-columns: 30% 25% 16% 5% 24%; +} + +.protocol_name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.runs_list { + display: flex; + width: 100%; + flex-direction: column; + gap: 0.25rem; +} diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsFileManager/__tests__/RobotSettingsFileManagers.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsFileManager/__tests__/RobotSettingsFileManagers.test.tsx deleted file mode 100644 index fd63c1ee4d5..00000000000 --- a/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsFileManager/__tests__/RobotSettingsFileManagers.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { screen } from '@testing-library/react' -import { beforeEach, describe, it, vi } from 'vitest' - -import { renderWithProviders } from '/app/__testing-utils__' - -import { RobotSettingsFileManager } from '../index' - -vi.mock('../RobotStorage', () => ({ - RobotStorage: () =>
mock robot storage
, -})) - -const render = () => renderWithProviders() - -describe('RobotSettingsFileManager', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - it('renders robot storage', () => { - render() - screen.getByText('mock robot storage') - }) -}) diff --git a/app/src/organisms/Desktop/Devices/constants.ts b/app/src/organisms/Desktop/Devices/constants.ts index df2c25e3a36..0ffc96e3b30 100644 --- a/app/src/organisms/Desktop/Devices/constants.ts +++ b/app/src/organisms/Desktop/Devices/constants.ts @@ -29,8 +29,3 @@ export function getDefaultTiprackDefForPipetteName( } return null } - -export const RECENT_PROTOCOL_RUNS_HEADER = '30% 25% 16% 5% 24%' - -// inclusive of overflow menu button -export const RECENT_PROTOCOL_RUNS_COLUMNS = '30% 25% 16% 5% 14% 10%' From 2d0c00a6784c38a943afdb9343db97dee3d21ead Mon Sep 17 00:00:00 2001 From: ncdiehl11 Date: Fri, 26 Jun 2026 14:28:43 -0400 Subject: [PATCH 5/6] use design tokens in CSS module --- .../RecentProtocolRuns/recentprotocolruns.module.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css index 938e3ab5a20..99c844e38e8 100644 --- a/app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns/recentprotocolruns.module.css @@ -4,7 +4,7 @@ flex-direction: column; align-items: flex-start; padding: 0 0 6rem; - border-radius: 8px; + border-radius: var(--border-radius-8); background-color: var(--white); } @@ -15,14 +15,14 @@ justify-content: space-between; padding: 1rem; border-bottom: 1px solid var(--grey-30); - gap: 0.5rem; + gap: var(--spacing-8); } .header_actions { display: flex; align-items: center; justify-content: flex-end; - gap: 0.5rem; + gap: var(--spacing-8); } .content { @@ -38,10 +38,10 @@ display: grid; width: 88%; justify-content: flex-start; - padding: 0.5rem; + padding: var(--spacing-8); margin-right: 12%; color: var(--grey-60); - gap: 1.25rem; + gap: var(--spacing-20); grid-template-columns: 30% 25% 16% 5% 24%; } @@ -55,5 +55,5 @@ display: flex; width: 100%; flex-direction: column; - gap: 0.25rem; + gap: var(--spacing-4); } From 2e49b8d54f03be03af869df627a52331d513e9b4 Mon Sep 17 00:00:00 2001 From: ncdiehl11 Date: Fri, 26 Jun 2026 14:38:55 -0400 Subject: [PATCH 6/6] move logic for DeleteRecordsModal content to IIF inside component --- .../hooks/useDeleteRecordsText.ts | 31 ----------------- .../Devices/DeleteRecordsModal/index.tsx | 34 +++++++++++++++++-- 2 files changed, 32 insertions(+), 33 deletions(-) delete mode 100644 app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts deleted file mode 100644 index f4afb966d96..00000000000 --- a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/hooks/useDeleteRecordsText.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useTranslation } from 'react-i18next' - -import type { DeleteRecordsType } from '../types' - -export function useDeleteRecordsText(type: DeleteRecordsType): { - title: string - description: string - recommendation: string -} { - const { t } = useTranslation('device_details') - switch (type) { - case 'allRuns': - return { - title: t('delete_all_run_records'), - description: t('delete_all_run_records_description'), - recommendation: t('delete_all_run_records_recommendation'), - } - case 'selectedRuns': - return { - title: t('delete_selected_run_records'), - description: t('delete_selected_run_records_description'), - recommendation: t('delete_selected_run_records_recommendation'), - } - case 'allLogs': - return { - title: t('delete_all_logs'), - description: t('delete_all_logs_description'), - recommendation: t('delete_all_logs_recommendation'), - } - } -} diff --git a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx index f017bec689b..63b7b45b782 100644 --- a/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx +++ b/app/src/organisms/Desktop/Devices/DeleteRecordsModal/index.tsx @@ -8,7 +8,6 @@ import { } from '@opentrons/components' import styles from './deleterecordsmodal.module.css' -import { useDeleteRecordsText } from './hooks/useDeleteRecordsText' import type { DeleteRecordsType } from './types' @@ -23,7 +22,38 @@ export function DeleteRecordsModal( ): JSX.Element { const { onClose, onConfirm, type } = props const { t } = useTranslation(['device_details', 'shared']) - const { title, description, recommendation } = useDeleteRecordsText(type) + const { title, description, recommendation } = ((): { + title: string + description: string + recommendation: string + } => { + switch (type) { + case 'allRuns': + return { + title: t('device_details:delete_all_run_records'), + description: t('device_details:delete_all_run_records_description'), + recommendation: t( + 'device_details:delete_all_run_records_recommendation' + ), + } + case 'selectedRuns': + return { + title: t('device_details:delete_selected_run_records'), + description: t( + 'device_details:delete_selected_run_records_description' + ), + recommendation: t( + 'device_details:delete_selected_run_records_recommendation' + ), + } + case 'allLogs': + return { + title: t('device_details:delete_all_logs'), + description: t('device_details:delete_all_logs_description'), + recommendation: t('device_details:delete_all_logs_recommendation'), + } + } + })() return (