diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx index 1ab8926f2d7..7bfeed69f28 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx @@ -140,7 +140,7 @@ export function ProtocolRunHeader({ const [showRunFailedModal, setShowRunFailedModal] = React.useState(false) const { data: runRecord } = useRunQuery(runId, { staleTime: Infinity }) const highestPriorityError = - runRecord?.data?.errors != null + runRecord?.data.errors?.[0] != null ? getHighestPriorityError(runRecord?.data?.errors) : undefined const { data: estopStatus, error: estopError } = useEstopQuery({ diff --git a/app/src/organisms/InstrumentInfo/__tests__/InstrumentInfo.test.tsx b/app/src/organisms/InstrumentInfo/__tests__/InstrumentInfo.test.tsx index a0b5f62fe33..23b380a3650 100644 --- a/app/src/organisms/InstrumentInfo/__tests__/InstrumentInfo.test.tsx +++ b/app/src/organisms/InstrumentInfo/__tests__/InstrumentInfo.test.tsx @@ -56,7 +56,7 @@ const mockGripperDataWithCalData: GripperData = { calibratedOffset: { offset: { x: 1, y: 2, z: 1 }, source: 'mockSource', - last_modified: 'mockLastModified', + last_modified: '2023-08-15T20:25', }, }, firmwareVersion: '12', @@ -97,7 +97,7 @@ describe('InstrumentInfo', () => { } const { getByText, getByRole } = render(props) getByText('last calibrated') - getByText('mockLastModified') + getByText('8/15/23 20:25 UTC') getByText('firmware version') getByText('12') getByText('serial number') @@ -114,7 +114,7 @@ describe('InstrumentInfo', () => { } const { getByText, getByRole } = render(props) getByText('last calibrated') - getByText('08/25/2020 20:25:00') + getByText('8/25/20 20:25 UTC') getByText('serial number') getByText('abc') getByRole('button', { name: 'MediumButton_secondary' }).click() diff --git a/app/src/organisms/InstrumentInfo/index.tsx b/app/src/organisms/InstrumentInfo/index.tsx index c12b0a94788..bc7b5fb2c31 100644 --- a/app/src/organisms/InstrumentInfo/index.tsx +++ b/app/src/organisms/InstrumentInfo/index.tsx @@ -21,8 +21,8 @@ import { StyledText } from '../../atoms/text' import { MediumButton } from '../../atoms/buttons' import { FLOWS } from '../PipetteWizardFlows/constants' import { useMaintenanceRunTakeover } from '../TakeoverModal' -import { formatTimestamp } from '../Devices/utils' import { GRIPPER_FLOW_TYPES } from '../GripperWizardFlows/constants' +import { formatTimeWithUtcLabel } from '../../resources/runs/utils' import type { InstrumentData } from '@opentrons/api-client' import type { PipetteMount } from '@opentrons/shared-data' @@ -128,7 +128,7 @@ export const InstrumentInfo = (props: InstrumentInfoProps): JSX.Element => { label={t('last_calibrated')} value={ instrument.data.calibratedOffset?.last_modified != null - ? formatTimestamp( + ? formatTimeWithUtcLabel( instrument.data.calibratedOffset?.last_modified ) : i18n.format(t('no_cal_data'), 'capitalize') diff --git a/app/src/organisms/InstrumentMountItem/LabeledMount.tsx b/app/src/organisms/InstrumentMountItem/LabeledMount.tsx index baf3c509785..b148e751cc7 100644 --- a/app/src/organisms/InstrumentMountItem/LabeledMount.tsx +++ b/app/src/organisms/InstrumentMountItem/LabeledMount.tsx @@ -40,7 +40,7 @@ interface LabeledMountProps { export function LabeledMount(props: LabeledMountProps): JSX.Element { const { t } = useTranslation('device_details') const { mount, instrumentName, handleClick } = props - const ninetySixDislayName = 'Flex 96-Channel 1000 μL' + const ninetySixDisplayName = 'Flex 96-Channel 1000 μL' return ( @@ -62,7 +62,7 @@ export function LabeledMount(props: LabeledMountProps): JSX.Element { fontSize={TYPOGRAPHY.fontSize28} width="15.625rem" > - {instrumentName === ninetySixDislayName + {instrumentName === ninetySixDisplayName ? t('left_right') : t('mount', { side: mount })} diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx index 6435a0d080f..e05db746405 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import { when, resetAllWhenMocks } from 'jest-when' import { Route } from 'react-router' import { MemoryRouter } from 'react-router-dom' -import { format } from 'date-fns' import '@testing-library/jest-dom' import { renderWithProviders } from '@opentrons/components' import { @@ -21,6 +20,7 @@ import { i18n } from '../../../../i18n' import { useMissingHardwareText } from '../../../../organisms/OnDeviceDisplay/RobotDashboard/hooks' import { useOffsetCandidatesForAnalysis } from '../../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { useMissingProtocolHardware } from '../../../Protocols/hooks' +import { formatTimeWithUtcLabel } from '../../../../resources/runs/utils' import { ProtocolDetails } from '..' import { Deck } from '../Deck' import { Hardware } from '../Hardware' @@ -171,9 +171,8 @@ describe('ODDProtocolDetails', () => { it('renders the protocol date added', () => { const [{ getByText }] = render() getByText( - `Date Added: ${format( - new Date('2022-05-03T21:36:12.494778+00:00'), - 'MM/dd/yy k:mm' + `Date Added: ${formatTimeWithUtcLabel( + '2022-05-03T21:36:12.494778+00:00' )}` ) }) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index 0eb8d925a0b..89cb0d2fc59 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -4,7 +4,6 @@ import { useQueryClient } from 'react-query' import { deleteProtocol, deleteRun, getProtocol } from '@opentrons/api-client' import { useDispatch, useSelector } from 'react-redux' import { useHistory, useParams } from 'react-router-dom' -import { format } from 'date-fns' import { ALIGN_CENTER, BORDERS, @@ -49,6 +48,7 @@ import { Deck } from './Deck' import { Hardware } from './Hardware' import { Labware } from './Labware' import { Liquids } from './Liquids' +import { formatTimeWithUtcLabel } from '../../../resources/runs/utils' import type { Protocol } from '@opentrons/api-client' import type { ModalHeaderBaseProps } from '../../../molecules/Modal/types' @@ -224,9 +224,7 @@ const Summary = ({ author, description, date }: SummaryProps): JSX.Element => { padding={`${SPACING.spacing8} ${SPACING.spacing12}`} > {`${t('protocol_info:date_added')}: ${ - date != null - ? format(new Date(date), 'MM/dd/yy k:mm') - : t('shared:no_data') + date != null ? formatTimeWithUtcLabel(date) : t('shared:no_data') }`} diff --git a/app/src/pages/OnDeviceDisplay/RunSummary.tsx b/app/src/pages/OnDeviceDisplay/RunSummary.tsx index 6bfec24840e..a98162a868a 100644 --- a/app/src/pages/OnDeviceDisplay/RunSummary.tsx +++ b/app/src/pages/OnDeviceDisplay/RunSummary.tsx @@ -21,6 +21,7 @@ import { OVERFLOW_HIDDEN, POSITION_ABSOLUTE, POSITION_RELATIVE, + WRAP, SPACING, TYPOGRAPHY, } from '@opentrons/components' @@ -51,11 +52,12 @@ import { ANALYTICS_PROTOCOL_RUN_AGAIN, ANALYTICS_PROTOCOL_RUN_FINISH, } from '../../redux/analytics' +import { getLocalRobot } from '../../redux/discovery' import { RunFailedModal } from '../../organisms/OnDeviceDisplay/RunningProtocol' +import { formatTimeWithUtcLabel } from '../../resources/runs/utils' import type { Run } from '@opentrons/api-client' import type { OnDeviceRouteParams } from '../../App/types' -import { getLocalRobot } from '../../redux/discovery' export function RunSummary(): JSX.Element { const { runId } = useParams() @@ -204,10 +206,10 @@ export function RunSummary(): JSX.Element { {headerText} {protocolName} - - {`${t( - 'run' - )}: ${createdAtTimestamp}`} + + + {`${t('run')}: ${formatTimeWithUtcLabel(createdAtTimestamp)}`} + {`${t('duration')}: `} - {`${t( - 'start' - )}: ${startedAtTimestamp}`} - {`${t( - 'end' - )}: ${completedAtTimestamp}`} + + {`${t('start')}: ${formatTimeWithUtcLabel( + startedAtTimestamp, + true + )}`} + + + {`${t('end')}: ${formatTimeWithUtcLabel( + completedAtTimestamp, + true + )}`} + diff --git a/app/src/pages/OnDeviceDisplay/__tests__/InstrumentsDashboard.test.tsx b/app/src/pages/OnDeviceDisplay/__tests__/InstrumentsDashboard.test.tsx index 75f8c58e48c..c2723236ba5 100644 --- a/app/src/pages/OnDeviceDisplay/__tests__/InstrumentsDashboard.test.tsx +++ b/app/src/pages/OnDeviceDisplay/__tests__/InstrumentsDashboard.test.tsx @@ -7,10 +7,10 @@ import { useInstrumentsQuery } from '@opentrons/react-api-client' import { i18n } from '../../../i18n' import { ChoosePipette } from '../../../organisms/PipetteWizardFlows/ChoosePipette' import { Navigation } from '../../../organisms/Navigation' -import { formatTimestamp } from '../../../organisms/Devices/utils' import { PipetteWizardFlows } from '../../../organisms/PipetteWizardFlows' import { GripperWizardFlows } from '../../../organisms/GripperWizardFlows' import { InstrumentsDashboard } from '../InstrumentsDashboard' +import { formatTimeWithUtcLabel } from '../../../resources/runs/utils' import { InstrumentDetail } from '../InstrumentDetail' jest.mock('@opentrons/react-api-client') @@ -140,7 +140,9 @@ describe('InstrumentsDashboard', () => { getByText('serial number') getByText(mockLeftPipetteData.serialNumber) getByText( - formatTimestamp(mockLeftPipetteData.data.calibratedOffset.last_modified) + formatTimeWithUtcLabel( + mockLeftPipetteData.data.calibratedOffset.last_modified + ) ) }) it('should route to right mount detail when instrument attached and clicked', async () => { @@ -149,7 +151,9 @@ describe('InstrumentsDashboard', () => { getByText('serial number') getByText(mockRightPipetteData.serialNumber) getByText( - formatTimestamp(mockRightPipetteData.data.calibratedOffset.last_modified) + formatTimeWithUtcLabel( + mockRightPipetteData.data.calibratedOffset.last_modified + ) ) }) it('should route to extension mount detail when instrument attached and clicked', async () => { diff --git a/app/src/pages/ProtocolDashboard/PinnedProtocol.tsx b/app/src/pages/ProtocolDashboard/PinnedProtocol.tsx index 310f259cb32..b2aebde769c 100644 --- a/app/src/pages/ProtocolDashboard/PinnedProtocol.tsx +++ b/app/src/pages/ProtocolDashboard/PinnedProtocol.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import { useHistory } from 'react-router-dom' -import { format, formatDistance } from 'date-fns' +import { formatDistance } from 'date-fns' import styled, { css } from 'styled-components' import { @@ -19,6 +19,7 @@ import { import { StyledText } from '../../atoms/text' import { LongPressModal } from './LongPressModal' +import { formatTimeWithUtcLabel } from '../../resources/runs/utils' import type { UseLongPressResult } from '@opentrons/components' import type { ProtocolResource } from '@opentrons/shared-data' @@ -131,7 +132,7 @@ export function PinnedProtocol(props: { : t('no_history')} - {format(new Date(protocol.createdAt), 'M/d/yy HH:mm')} + {formatTimeWithUtcLabel(protocol.createdAt)} {longpress.isLongPressed && ( diff --git a/app/src/pages/ProtocolDashboard/ProtocolCard.tsx b/app/src/pages/ProtocolDashboard/ProtocolCard.tsx index 4dc78afd6c4..f69da042383 100644 --- a/app/src/pages/ProtocolDashboard/ProtocolCard.tsx +++ b/app/src/pages/ProtocolDashboard/ProtocolCard.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useHistory } from 'react-router-dom' import { Trans, useTranslation } from 'react-i18next' import { useQueryClient } from 'react-query' -import { format, formatDistance } from 'date-fns' +import { formatDistance } from 'date-fns' import last from 'lodash/last' import { css } from 'styled-components' @@ -15,7 +15,6 @@ import { DIRECTION_ROW, Flex, Icon, - JUSTIFY_CENTER, SPACING, TYPOGRAPHY, useLongPress, @@ -27,6 +26,7 @@ import { StyledText } from '../../atoms/text' import { SmallButton } from '../../atoms/buttons' import { Modal } from '../../molecules/Modal' import { LongPressModal } from './LongPressModal' +import { formatTimeWithUtcLabel } from '../../resources/runs/utils' import type { UseLongPressResult } from '@opentrons/components' import type { ProtocolResource } from '@opentrons/shared-data' @@ -149,7 +149,7 @@ export function ProtocolCard(props: { css={PUSHED_STATE_STYLE} > - + {lastRun != null ? formatDistance(new Date(lastRun), new Date(), { @@ -183,9 +183,9 @@ export function ProtocolCard(props: { : t('no_history')} - + - {format(new Date(protocol.createdAt), 'M/d/yy HH:mm')} + {formatTimeWithUtcLabel(protocol.createdAt)} {longpress.isLongPressed && !isFailedAnalysis && ( - + - + { + it('return formatted time with UTC', () => { + const result = formatTimeWithUtcLabel('2023-08-20T20:25') + expect(result).toEqual('8/20/23 20:25 UTC') + }) + it('return formatted time with UTC without T', () => { + const result = formatTimeWithUtcLabel('08/22/2023 21:35:04') + expect(result).toEqual('8/22/23 21:35 UTC') + }) + + it('return formatted time with UTC only hh:mm', () => { + const result = formatTimeWithUtcLabel('21:35:04', true) + expect(result).toEqual('21:35:04 UTC') + }) +}) diff --git a/app/src/resources/runs/utils.ts b/app/src/resources/runs/utils.ts index 398edc16942..7ab3aa5444c 100644 --- a/app/src/resources/runs/utils.ts +++ b/app/src/resources/runs/utils.ts @@ -1,4 +1,5 @@ import * as React from 'react' +import { format } from 'date-fns' import type { CommandData } from '@opentrons/api-client' import type { CreateCommand } from '@opentrons/shared-data' import type { CreateMaintenanceCommand, CreateRunCommand } from './hooks' @@ -85,3 +86,15 @@ export const chainMaintenanceCommandsRecursive = ( return Promise.reject(error) }) } + +export const formatTimeWithUtcLabel = ( + time: string, + noFormat?: boolean +): string => { + const UTC_LABEL = 'UTC' + const TIME_FORMAT = 'M/d/yy HH:mm' + + return noFormat + ? `${time} ${UTC_LABEL}` + : `${format(new Date(time), TIME_FORMAT)} ${UTC_LABEL}` +}