Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
useHoverTooltip,
} from '@opentrons/components'
import { useAddLabwareOffsetToRunMutation } from '@opentrons/react-api-client'
import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data'

import { useLPCAnalytics } from '/app/organisms/LabwarePositionCheck/LPCFlows'
import { useToaster } from '/app/organisms/ToasterOven'
import {
selectIsAnyNecessaryDefaultOffsetMissing,
Expand All @@ -39,6 +41,10 @@ export function LPCSetupFlexBtns({
}: LPCSetupFlexBtnsProps): JSX.Element {
const { t } = useTranslation('protocol_setup')
const { makeSnackbar } = useToaster()
const { reportApplyOffsets } = useLPCAnalytics({
runId,
robotType: FLEX_ROBOT_TYPE,
})
const lpcDisabledReason = useLPCDisabledReason({
robotName,
runId,
Expand Down Expand Up @@ -103,6 +109,7 @@ export function LPCSetupFlexBtns({
)
.then(() => {
setOffsetsConfirmed(true)
reportApplyOffsets()
})
.catch(() => {
makeSnackbar(t('failed_to_apply_offsets') as string)
Expand Down
8 changes: 8 additions & 0 deletions app/src/organisms/LabwareOffsetsConflictModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import {
SPACING,
StyledText,
} from '@opentrons/components'
import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data'

import { getModalPortalEl, getTopPortalEl } from '/app/App/portal'
import { SmallButton } from '/app/atoms/buttons'
import { OddModal } from '/app/molecules/OddModal'
import { useLPCAnalytics } from '/app/organisms/LabwarePositionCheck'
import {
selectConflictTimestampInfo,
sourceOffsetsFromDatabase,
Expand All @@ -43,13 +45,19 @@ export function LabwareOffsetsConflictModal({
const dispatch = useDispatch()
const tsInfo = useSelector(selectConflictTimestampInfo(runId))
const tsFormatted = formatTimestamp(tsInfo.timestamp ?? '')
const { reportOffsetSourceResolution } = useLPCAnalytics({
runId,
robotType: FLEX_ROBOT_TYPE,
})

const onRunRecordOffsets = (): void => {
dispatch(sourceOffsetsFromRun(runId))
reportOffsetSourceResolution('fromRunRecord')
}

const onDatabaseOffsets = (): void => {
dispatch(sourceOffsetsFromDatabase(runId))
reportOffsetSourceResolution('fromDatabase')
}

return createPortal(
Expand Down
2 changes: 2 additions & 0 deletions app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
LabwareDefinition2,
RobotType,
} from '@opentrons/shared-data'
import type { useLPCAnalytics } from '/app/organisms/LabwarePositionCheck'
import type { LPCLabwareInfo } from '/app/redux/protocol-runs'

// Inject the props specific to the legacy LPC flows, too.
Expand All @@ -25,6 +26,7 @@ export interface LPCFlowsProps {
analysis: CompletedProtocolAnalysis
protocolName: string
maintenanceRunId: string
analytics: ReturnType<typeof useLPCAnalytics>
}

export function LPCFlows(props: LegacySupportLPCFlowsProps): JSX.Element {
Expand Down
1 change: 1 addition & 0 deletions app/src/organisms/LabwarePositionCheck/LPCFlows/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useLPCFlows } from './useLPCFlows'
export { useLPCAnalytics } from './useLPCAnalytics'
export * from './LPCFlows'

export type { UseLPCFlowsProps, UseLPCFlowsResult } from './useLPCFlows'
184 changes: 184 additions & 0 deletions app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCAnalytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { useState } from 'react'

import { ANY_LOCATION } from '@opentrons/api-client'
import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data'

import {
ANALYTICS_LPC_APPLY_OFFSETS,
ANALYTICS_LPC_LAUNCH,
ANALYTICS_LPC_OFFSET_SOURCE_RESOLUTION,
ANALYTICS_LPC_SAVE_OFFSET,
ANALYTICS_LPC_SAVE_OFFSET_TO_RUN_RECORD,
useTrackEvent,
} from '/app/redux/analytics'

import type {
LegacyLabwareOffsetLocation,
StoredLabwareOffset,
} from '@opentrons/api-client'
import type { RobotType, Vector3D } from '@opentrons/shared-data'
import type { ResolvedOffsetSource } from '/app/redux/protocol-runs'

interface ReportSaveOffsetToRunRecordParams {
uri: string
locationDetails: LegacyLabwareOffsetLocation
vector: Vector3D
slot: string
}

export interface UseLPCAnalyticsProps {
runId: string
robotType: RobotType
}

export interface UseLPCAnalyticsResult {
/* Report when a user launches LPC, generating a wizard session id. */
reportLaunchLpcWizard: () => void
/* Report when a user clicks the 'apply offsets' button. Effectively a Flex only event. */
reportApplyOffsets: () => void
/* Report all the modified offsets when a user saves to the database. Flex only. */
reportSaveOffset: (
params: [StoredLabwareOffset[], StoredLabwareOffset[]]
) => void
/* Report when an offset is `saved` to the run record. OT-2 only. */
reportSaveOffsetToRunRecord: (
params: ReportSaveOffsetToRunRecordParams
) => void
/* Report the user-selected option whenever there is an offset source conflict. */
reportOffsetSourceResolution: (resolvedSource: ResolvedOffsetSource) => void
}

export function useLPCAnalytics({
runId,
robotType,
}: UseLPCAnalyticsProps): UseLPCAnalyticsResult {
const doTrackEvent = useTrackEvent()
const [lpcWizardSessionId, setLpcWizardSessionId] = useState<string | null>(
null
)

const reportLaunchLpcWizard = (): void => {
const wizardSessionId = `${runId}-${Date.now()}`
setLpcWizardSessionId(wizardSessionId)

doTrackEvent({
name: ANALYTICS_LPC_LAUNCH,
properties: {
robotType,
runSession: runId,
wizardSession: wizardSessionId,
},
})
}

// Offsets are applied outside the LPC wizard and therefore not tied to a wizard session.
const reportApplyOffsets = (): void => {
if (robotType === OT2_ROBOT_TYPE) {
console.error('OT2 robot type should not report apply offsets.')
} else {
doTrackEvent({
name: ANALYTICS_LPC_APPLY_OFFSETS,
properties: {
robotType,
runSession: runId,
},
})
}
}

const reportSaveOffset = (
params: [StoredLabwareOffset[], StoredLabwareOffset[]]
): void => {
if (robotType === OT2_ROBOT_TYPE) {
console.error(
'OT-2 should not report save offset. Use reportSaveOffsetToRunRecord instead.'
)
} else {
const sendSaveOffsetEvent = (offset: StoredLabwareOffset): void => {
const offsetKind =
offset.locationSequence === ANY_LOCATION
? 'default'
: 'appliedLocation'

const slot = offset.locationSequence[offset.locationSequence.length - 1]

const slotName = ((): string | null => {
if (typeof slot === 'string') {
return null
} else if (slot.kind === 'onAddressableArea') {
return slot.addressableAreaName
}
// The last location sequence component in a LabwareOffsetLocationSequence is
// always the addressable area, but we handle unexpected input to be safe.
else {
return null
}
})()

doTrackEvent({
name: ANALYTICS_LPC_SAVE_OFFSET,
properties: {
robotType,
runSession: runId,
wizardSession: lpcWizardSessionId,
uri: offset.definitionUri,
locationDetails: offset.locationSequence,
slot: slotName,
vector: offset.vector,
offsetKind,
},
})
}
const [updatedOffsets, deletedOffsets] = params

updatedOffsets.forEach(sendSaveOffsetEvent)
deletedOffsets.forEach(sendSaveOffsetEvent)
}
}

const reportSaveOffsetToRunRecord = (
params: ReportSaveOffsetToRunRecordParams
): void => {
if (robotType === FLEX_ROBOT_TYPE) {
console.error(
'Flex should not report save offset to run record. Use reportSaveOffset instead.'
)
} else {
doTrackEvent({
name: ANALYTICS_LPC_SAVE_OFFSET_TO_RUN_RECORD,
properties: {
robotType,
runSession: runId,
wizardSession: lpcWizardSessionId,
...params,
},
})
}
}

// Offset conflicts are resolved outside the LPC wizard and therefore not tied to a wizard session.
const reportOffsetSourceResolution = (
resolvedSource: ResolvedOffsetSource
): void => {
if (robotType === OT2_ROBOT_TYPE) {
console.error('OT-2 should not report offset source resolution.')
} else {
doTrackEvent({
name: ANALYTICS_LPC_OFFSET_SOURCE_RESOLUTION,
properties: {
robotType,
runSession: runId,
resolvedSource,
},
})
}
}

return {
reportLaunchLpcWizard,
reportApplyOffsets,
reportSaveOffset,
reportSaveOffsetToRunRecord,
reportOffsetSourceResolution,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
useUpdateDeckConfig,
useUpdateLabware,
} from './hooks'
import { useLPCAnalytics } from './useLPCAnalytics'

import type { RobotType } from '@opentrons/shared-data'
import type {
Expand Down Expand Up @@ -61,6 +62,11 @@ export function useLPCFlows({
robotType,
protocolName,
}: UseLPCFlowsProps): UseLPCFlowsResult {
const analytics = useLPCAnalytics({
robotType,
runId: runId ?? 'UNKNOWN',
})

const [maintenanceRunId, setMaintenanceRunId] = useState<string | null>(null)
const [isLaunching, setIsLaunching] = useState(false)
const [hasCreatedLPCRun, setHasCreatedLPCRun] = useState(false)
Expand Down Expand Up @@ -150,6 +156,7 @@ export function useLPCFlows({
const launchLPC = (): Promise<void> => {
// Avoid accidentally creating several maintenance runs if a request is ongoing.
if (!isLaunching) {
analytics.reportLaunchLpcWizard()
setIsLaunching(true)
const labwareOffsets = getRelevantOffsets(
robotType,
Expand Down Expand Up @@ -208,6 +215,7 @@ export function useLPCFlows({
protocolName,
maintenanceRunId,
ot2Offsets,
analytics,
},
}
: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const mockLPCContentProps: LPCWizardContentProps = {
commandUtils: {} as any,
proceedStep: vi.fn(),
goBackLastStep: vi.fn(),
analytics: {} as any,
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('useLPCHeaderCommands', () => {
proceedStep: mockProceedStep,
goBackLastStep: vi.fn(),
runId: mockRunId,
analytics: {} as any,
}

store = createStore(vi.fn(), {})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ vi.mock('/app/redux/protocol-runs')

describe('useSaveWorkingOffsets', () => {
const mockRunId = 'mock_run_id'
const mockReportSaveOffset = vi.fn()

const mockProps = {
runId: mockRunId,
analytics: { reportSaveOffset: mockReportSaveOffset },
} as any

const mockCreateLabwareOffsets = vi.fn()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface UseBuildOffsetsToApplyResult {

export function useSaveWorkingOffsets({
runId,
analytics,
}: UseLPCCommandChildProps): UseBuildOffsetsToApplyResult {
const [isLoading, setIsLoading] = useState(false)

Expand Down Expand Up @@ -58,6 +59,7 @@ export function useSaveWorkingOffsets({
])
.then(res => {
setIsLoading(false)
analytics.reportSaveOffset(res)

return res
})
Expand Down
5 changes: 4 additions & 1 deletion app/src/organisms/LabwarePositionCheck/types/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import type { UseLPCHeaderCommandsResult } from '/app/organisms/LabwarePositionC
import type { LPCWizardFlexProps } from '/app/organisms/LabwarePositionCheck/LPCWizardFlex'
import type { LPCStep } from '/app/redux/protocol-runs'

export type LPCWizardContentProps = Pick<LPCWizardFlexProps, 'runId'> & {
export type LPCWizardContentProps = Pick<
LPCWizardFlexProps,
'runId' | 'analytics'
> & {
proceedStep: (toStep?: LPCStep) => void
goBackLastStep: () => void
commandUtils: UseLPCCommandsResult & {
Expand Down
Loading
Loading