Skip to content

Commit 00d338d

Browse files
authored
refactor(app): create new protocol run analytics events (#11112)
replaces the deprecated protocol upload events used in 5.0 and earlier. refactors the protocol run analytics hooks to extract helpers that rely on the protocol record directly instead of the run id re #10750
1 parent 8b43995 commit 00d338d

File tree

12 files changed

+373
-84
lines changed

12 files changed

+373
-84
lines changed

app/src/organisms/ChooseProtocolSlideout/__tests__/ChooseProtocolSlideout.test.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import { getStoredProtocols } from '../../../redux/protocol-storage'
77
import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__'
88
import { storedProtocolData as storedProtocolDataFixture } from '../../../redux/protocol-storage/__fixtures__'
99
import { DeckThumbnail } from '../../../molecules/DeckThumbnail'
10+
import { useTrackCreateProtocolRunEvent } from '../../../organisms/Devices/hooks'
1011
import { useCreateRunFromProtocol } from '../../ChooseRobotSlideout/useCreateRunFromProtocol'
1112
import { ChooseProtocolSlideout } from '../'
1213

1314
jest.mock('../../ChooseRobotSlideout/useCreateRunFromProtocol')
1415
jest.mock('../../../redux/protocol-storage')
1516
jest.mock('../../../molecules/DeckThumbnail')
17+
jest.mock('../../../organisms/Devices/hooks')
1618

1719
const mockGetStoredProtocols = getStoredProtocols as jest.MockedFunction<
1820
typeof getStoredProtocols
@@ -23,6 +25,9 @@ const mockUseCreateRunFromProtocol = useCreateRunFromProtocol as jest.MockedFunc
2325
const mockDeckThumbnail = DeckThumbnail as jest.MockedFunction<
2426
typeof DeckThumbnail
2527
>
28+
const mockUseTrackCreateProtocolRunEvent = useTrackCreateProtocolRunEvent as jest.MockedFunction<
29+
typeof useTrackCreateProtocolRunEvent
30+
>
2631

2732
const render = (props: React.ComponentProps<typeof ChooseProtocolSlideout>) => {
2833
return renderWithProviders(
@@ -37,13 +42,20 @@ const render = (props: React.ComponentProps<typeof ChooseProtocolSlideout>) => {
3742

3843
describe('ChooseProtocolSlideout', () => {
3944
let mockCreateRunFromProtocol: jest.Mock
45+
let mockTrackCreateProtocolRunEvent: jest.Mock
4046
beforeEach(() => {
4147
mockCreateRunFromProtocol = jest.fn()
48+
mockTrackCreateProtocolRunEvent = jest.fn(
49+
() => new Promise(resolve => resolve({}))
50+
)
4251
mockGetStoredProtocols.mockReturnValue([storedProtocolDataFixture])
4352
mockDeckThumbnail.mockReturnValue(<div>mock Deck Thumbnail</div>)
4453
mockUseCreateRunFromProtocol.mockReturnValue({
4554
createRunFromProtocolSource: mockCreateRunFromProtocol,
4655
} as any)
56+
mockUseTrackCreateProtocolRunEvent.mockReturnValue({
57+
trackCreateProtocolRunEvent: mockTrackCreateProtocolRunEvent,
58+
})
4759
})
4860
afterEach(() => {
4961
jest.resetAllMocks()
@@ -102,6 +114,7 @@ describe('ChooseProtocolSlideout', () => {
102114
files: [expect.any(File)],
103115
protocolKey: storedProtocolDataFixture.protocolKey,
104116
})
117+
expect(mockTrackCreateProtocolRunEvent).toHaveBeenCalled()
105118
})
106119
it('renders error state when there is a run creation error', () => {
107120
mockUseCreateRunFromProtocol.mockReturnValue({
@@ -122,6 +135,7 @@ describe('ChooseProtocolSlideout', () => {
122135
files: [expect.any(File)],
123136
protocolKey: storedProtocolDataFixture.protocolKey,
124137
})
138+
expect(mockTrackCreateProtocolRunEvent).toHaveBeenCalled()
125139
expect(getByText('run creation error')).toBeInTheDocument()
126140
})
127141

@@ -144,6 +158,7 @@ describe('ChooseProtocolSlideout', () => {
144158
files: [expect.any(File)],
145159
protocolKey: storedProtocolDataFixture.protocolKey,
146160
})
161+
expect(mockTrackCreateProtocolRunEvent).toHaveBeenCalled()
147162
getByText('This robot is busy and can’t run this protocol right now.')
148163
const link = getByRole('link', { name: 'Go to Robot' })
149164
fireEvent.click(link)

app/src/organisms/ChooseProtocolSlideout/index.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { PrimaryButton } from '../../atoms/buttons'
3030
import { StyledText } from '../../atoms/text'
3131
import { MiniCard } from '../../molecules/MiniCard'
3232
import { DeckThumbnail } from '../../molecules/DeckThumbnail'
33+
import { useTrackCreateProtocolRunEvent } from '../Devices/hooks'
3334
import { useCreateRunFromProtocol } from '../ChooseRobotSlideout/useCreateRunFromProtocol'
3435

3536
import type { Robot } from '../../redux/discovery/types'
@@ -57,6 +58,10 @@ export function ChooseProtocolSlideout(
5758
setSelectedProtocol,
5859
] = React.useState<StoredProtocolData | null>(first(storedProtocols) ?? null)
5960

61+
const { trackCreateProtocolRunEvent } = useTrackCreateProtocolRunEvent(
62+
selectedProtocol
63+
)
64+
6065
const srcFileObjects =
6166
selectedProtocol != null
6267
? selectedProtocol.srcFiles.map((srcFileBuffer, index) => {
@@ -73,12 +78,23 @@ export function ChooseProtocolSlideout(
7378
runCreationErrorCode,
7479
} = useCreateRunFromProtocol({
7580
onSuccess: ({ data: runData }) => {
81+
trackCreateProtocolRunEvent({
82+
name: 'createProtocolRecordResponse',
83+
properties: { success: true },
84+
})
7685
history.push(`/devices/${name}/protocol-runs/${runData.id}`)
7786
},
87+
onError: (error: Error) => {
88+
trackCreateProtocolRunEvent({
89+
name: 'createProtocolRecordResponse',
90+
properties: { success: false, error: error.message },
91+
})
92+
},
7893
})
7994

8095
const handleProceed: React.MouseEventHandler<HTMLButtonElement> = () => {
8196
if (selectedProtocol != null) {
97+
trackCreateProtocolRunEvent({ name: 'createProtocolRecordRequest' })
8298
createRunFromProtocolSource({
8399
files: srcFileObjects,
84100
protocolKey: selectedProtocol.protocolKey,

app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { fireEvent } from '@testing-library/react'
55
import { when } from 'jest-when'
66

77
import { i18n } from '../../../i18n'
8-
import { useProtocolDetailsForRun } from '../../../organisms/Devices/hooks'
8+
import {
9+
useProtocolDetailsForRun,
10+
useTrackCreateProtocolRunEvent,
11+
} from '../../../organisms/Devices/hooks'
912
import {
1013
useCloseCurrentRun,
1114
useCurrentRunId,
@@ -72,6 +75,9 @@ const mockUseProtocolDetailsForRun = useProtocolDetailsForRun as jest.MockedFunc
7275
const mockUseCreateRunFromProtocol = useCreateRunFromProtocol as jest.MockedFunction<
7376
typeof useCreateRunFromProtocol
7477
>
78+
const mockUseTrackCreateProtocolRunEvent = useTrackCreateProtocolRunEvent as jest.MockedFunction<
79+
typeof useTrackCreateProtocolRunEvent
80+
>
7581

7682
const render = (props: React.ComponentProps<typeof ChooseRobotSlideout>) => {
7783
return renderWithProviders(
@@ -87,12 +93,16 @@ const render = (props: React.ComponentProps<typeof ChooseRobotSlideout>) => {
8793
let mockCloseCurrentRun: jest.Mock
8894
let mockResetCreateRun: jest.Mock
8995
let mockCreateRunFromProtocolSource: jest.Mock
96+
let mockTrackCreateProtocolRunEvent: jest.Mock
9097

9198
describe('ChooseRobotSlideout', () => {
9299
beforeEach(() => {
93100
mockCloseCurrentRun = jest.fn()
94101
mockResetCreateRun = jest.fn()
95102
mockCreateRunFromProtocolSource = jest.fn()
103+
mockTrackCreateProtocolRunEvent = jest.fn(
104+
() => new Promise(resolve => resolve({}))
105+
)
96106
mockGetBuildrootUpdateDisplayInfo.mockReturnValue({
97107
autoUpdateAction: '',
98108
autoUpdateDisabledReason: null,
@@ -116,6 +126,9 @@ describe('ChooseRobotSlideout', () => {
116126
createRunFromProtocolSource: mockCreateRunFromProtocolSource,
117127
reset: mockResetCreateRun,
118128
} as any)
129+
mockUseTrackCreateProtocolRunEvent.mockReturnValue({
130+
trackCreateProtocolRunEvent: mockTrackCreateProtocolRunEvent,
131+
})
119132
})
120133
afterEach(() => {
121134
jest.resetAllMocks()
@@ -196,6 +209,7 @@ describe('ChooseRobotSlideout', () => {
196209
files: [expect.any(File)],
197210
protocolKey: storedProtocolDataFixture.protocolKey,
198211
})
212+
expect(mockTrackCreateProtocolRunEvent).toHaveBeenCalled()
199213
})
200214
it('if selected robot is on a different version of the software than the app, disable CTA and show link to device details in options', () => {
201215
when(mockGetBuildrootUpdateDisplayInfo)
@@ -240,6 +254,7 @@ describe('ChooseRobotSlideout', () => {
240254
files: [expect.any(File)],
241255
protocolKey: storedProtocolDataFixture.protocolKey,
242256
})
257+
expect(mockTrackCreateProtocolRunEvent).toHaveBeenCalled()
243258
expect(getByText('run creation error')).toBeInTheDocument()
244259
})
245260

@@ -262,6 +277,7 @@ describe('ChooseRobotSlideout', () => {
262277
files: [expect.any(File)],
263278
protocolKey: storedProtocolDataFixture.protocolKey,
264279
})
280+
expect(mockTrackCreateProtocolRunEvent).toHaveBeenCalled()
265281
getByText('This robot is busy and can’t run this protocol right now.')
266282
const link = getByRole('link', { name: 'Go to Robot' })
267283
fireEvent.click(link)

app/src/organisms/ChooseRobotSlideout/index.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { PrimaryButton } from '../../atoms/buttons'
3838
import { Slideout } from '../../atoms/Slideout'
3939
import { StyledText } from '../../atoms/text'
4040
import { StoredProtocolData } from '../../redux/protocol-storage'
41+
import { useTrackCreateProtocolRunEvent } from '../Devices/hooks'
4142
import { AvailableRobotOption } from './AvailableRobotOption'
4243
import { useCreateRunFromProtocol } from './useCreateRunFromProtocol'
4344

@@ -59,6 +60,10 @@ export function ChooseRobotSlideout(
5960
const history = useHistory()
6061
const isScanning = useSelector((state: State) => getScanning(state))
6162

63+
const { trackCreateProtocolRunEvent } = useTrackCreateProtocolRunEvent(
64+
storedProtocolData
65+
)
66+
6267
const unhealthyReachableRobots = useSelector((state: State) =>
6368
getReachableRobots(state)
6469
)
@@ -81,15 +86,26 @@ export function ChooseRobotSlideout(
8186
{
8287
onSuccess: ({ data: runData }) => {
8388
if (selectedRobot != null) {
89+
trackCreateProtocolRunEvent({
90+
name: 'createProtocolRecordResponse',
91+
properties: { success: true },
92+
})
8493
history.push(
8594
`/devices/${selectedRobot.name}/protocol-runs/${runData.id}`
8695
)
8796
}
8897
},
98+
onError: (error: Error) => {
99+
trackCreateProtocolRunEvent({
100+
name: 'createProtocolRecordResponse',
101+
properties: { success: false, error: error.message },
102+
})
103+
},
89104
},
90105
selectedRobot != null ? { hostname: selectedRobot.ip } : null
91106
)
92107
const handleProceed: React.MouseEventHandler<HTMLButtonElement> = () => {
108+
trackCreateProtocolRunEvent({ name: 'createProtocolRecordRequest' })
93109
createRunFromProtocolSource({ files: srcFileObjects, protocolKey })
94110
}
95111

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { storedProtocolData } from '../../../../redux/protocol-storage/__fixtures__'
2+
3+
import type {
4+
LoadedLabwareById,
5+
LoadedLabwareDefinitionsById,
6+
ModuleModelsById,
7+
PipetteNamesById,
8+
} from '@opentrons/api-client'
9+
import type { LabwareDefinition2 } from '@opentrons/shared-data'
10+
import type { StoredProtocolAnalysis } from '../useStoredProtocolAnalysis'
11+
12+
export const LABWARE_BY_ID: LoadedLabwareById = {
13+
'labware-0': {
14+
definitionId: 'fakeLabwareDefinitionId',
15+
displayName: 'a fake labware',
16+
},
17+
}
18+
export const LABWARE_DEFINITIONS: LoadedLabwareDefinitionsById = {
19+
fakeLabwareDefinitionId: {} as LabwareDefinition2,
20+
}
21+
export const MODULE_MODELS_BY_ID: ModuleModelsById = {
22+
'module-0': { model: 'thermocyclerModuleV1' },
23+
}
24+
export const PIPETTE_NAMES_BY_ID: PipetteNamesById = {
25+
'pipette-0': { name: 'p10_single' },
26+
}
27+
28+
export const STORED_PROTOCOL_ANALYSIS = {
29+
...storedProtocolData.mostRecentAnalysis,
30+
modules: MODULE_MODELS_BY_ID,
31+
labware: LABWARE_BY_ID,
32+
labwareDefinitions: LABWARE_DEFINITIONS,
33+
pipettes: PIPETTE_NAMES_BY_ID,
34+
} as StoredProtocolAnalysis

app/src/organisms/Devices/hooks/__tests__/useStoredProtocolAnalysis.test.tsx

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@ import { useProtocolQuery, useRunQuery } from '@opentrons/react-api-client'
1616
import { storedProtocolData } from '../../../../redux/protocol-storage/__fixtures__'
1717
import { getStoredProtocol } from '../../../../redux/protocol-storage'
1818
import { useStoredProtocolAnalysis } from '../useStoredProtocolAnalysis'
19+
import {
20+
LABWARE_BY_ID,
21+
LABWARE_DEFINITIONS,
22+
MODULE_MODELS_BY_ID,
23+
PIPETTE_NAMES_BY_ID,
24+
STORED_PROTOCOL_ANALYSIS,
25+
} from '../__fixtures__/storedProtocolAnalysis'
1926

20-
import type {
21-
LoadedLabwareById,
22-
LoadedLabwareDefinitionsById,
23-
ModuleModelsById,
24-
PipetteNamesById,
25-
Protocol,
26-
Run,
27-
} from '@opentrons/api-client'
28-
import type { LabwareDefinition2 } from '@opentrons/shared-data'
27+
import type { Protocol, Run } from '@opentrons/api-client'
2928

3029
jest.mock('@opentrons/api-client')
3130
jest.mock('@opentrons/react-api-client')
@@ -56,22 +55,6 @@ const RUN_ID = 'the_run_id'
5655
const PROTOCOL_ID = 'the_protocol_id'
5756
const PROTOCOL_KEY = 'the_protocol_key'
5857

59-
const LABWARE_BY_ID: LoadedLabwareById = {
60-
'labware-0': {
61-
definitionId: 'fakeLabwareDefinitionId',
62-
displayName: 'a fake labware',
63-
},
64-
}
65-
const LABWARE_DEFINITIONS: LoadedLabwareDefinitionsById = {
66-
fakeLabwareDefinitionId: {} as LabwareDefinition2,
67-
}
68-
const MODULE_MODELS_BY_ID: ModuleModelsById = {
69-
'module-0': { model: 'thermocyclerModuleV1' },
70-
}
71-
const PIPETTE_NAMES_BY_ID: PipetteNamesById = {
72-
'pipette-0': { name: 'p10_single' },
73-
}
74-
7558
describe('useStoredProtocolAnalysis hook', () => {
7659
let wrapper: React.FunctionComponent<{}>
7760
beforeEach(() => {
@@ -156,12 +139,6 @@ describe('useStoredProtocolAnalysis hook', () => {
156139
wrapper,
157140
})
158141

159-
expect(result.current).toEqual({
160-
...storedProtocolData.mostRecentAnalysis,
161-
modules: MODULE_MODELS_BY_ID,
162-
labware: LABWARE_BY_ID,
163-
labwareDefinitions: LABWARE_DEFINITIONS,
164-
pipettes: PIPETTE_NAMES_BY_ID,
165-
})
142+
expect(result.current).toEqual(STORED_PROTOCOL_ANALYSIS)
166143
})
167144
})

0 commit comments

Comments
 (0)