Skip to content

Commit

Permalink
feat(app): add simple aggregate event tracking for custom labware (#4544
Browse files Browse the repository at this point in the history
)


Closes #4537
  • Loading branch information
mcous authored and sfoster1 committed Dec 5, 2019
1 parent 8adf312 commit b4fd536
Show file tree
Hide file tree
Showing 17 changed files with 328 additions and 25 deletions.
31 changes: 27 additions & 4 deletions app-shell/src/labware/__tests__/dispatch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,26 @@ describe('labware module dispatches', () => {

changeHandler()

return flush().then(() =>
return flush().then(() => {
expect(readLabwareDirectory).toHaveBeenCalledWith(labwareDir)
)
expect(dispatch).toHaveBeenCalledWith(
CustomLabware.customLabwareList([], 'changeDirectory')
)
})
})

test('dispatches labware directory list error on config change', () => {
const changeHandler = handleConfigChange.mock.calls[0][1]

readLabwareDirectory.mockRejectedValue((new Error('AH'): any))
changeHandler()

return flush().then(() => {
expect(readLabwareDirectory).toHaveBeenCalledWith(labwareDir)
expect(dispatch).toHaveBeenCalledWith(
CustomLabware.customLabwareListFailure('AH', 'changeDirectory')
)
})
})

test('opens file picker on ADD_CUSTOM_LABWARE', () => {
Expand Down Expand Up @@ -270,7 +287,10 @@ describe('labware module dispatches', () => {

test('adds file and triggers a re-scan if valid', () => {
const mockValidFile = CustomLabwareFixtures.mockValidLabware
const expectedAction = CustomLabware.customLabwareList([mockValidFile])
const expectedAction = CustomLabware.customLabwareList(
[mockValidFile],
'addLabware'
)

showOpenFileDialog.mockResolvedValue([mockValidFile.filename])
validateNewLabwareFile.mockReturnValueOnce(mockValidFile)
Expand Down Expand Up @@ -314,7 +334,10 @@ describe('labware module dispatches', () => {
({ ...duplicate, filename: '/duplicate2.json' }: DuplicateLabwareFile),
]
const mockAfterDeletes = [CustomLabwareFixtures.mockValidLabware]
const expectedAction = CustomLabware.customLabwareList(mockAfterDeletes)
const expectedAction = CustomLabware.customLabwareList(
mockAfterDeletes,
'overwriteLabware'
)

// validation of existing definitions
validateLabwareFiles.mockReturnValueOnce(mockExisting)
Expand Down
24 changes: 17 additions & 7 deletions app-shell/src/labware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as ConfigActions from '@opentrons/app/src/config'
import type {
UncheckedLabwareFile,
DuplicateLabwareFile,
CustomLabwareListActionSource as ListSource,
} from '@opentrons/app/src/custom-labware/types'
import type { Action, Dispatch } from '../types'

Expand All @@ -25,14 +26,17 @@ const fetchCustomLabware = (): Promise<Array<UncheckedLabwareFile>> => {
.then(Definitions.parseLabwareFiles)
}

const fetchAndValidateCustomLabware = (dispatch: Dispatch): Promise<void> => {
const fetchAndValidateCustomLabware = (
dispatch: Dispatch,
source: ListSource
): Promise<void> => {
return fetchCustomLabware()
.then(files => {
const payload = validateLabwareFiles(files)
dispatch(CustomLabware.customLabwareList(payload))
dispatch(CustomLabware.customLabwareList(payload, source))
})
.catch((error: Error) => {
dispatch(CustomLabware.customLabwareListFailure(error.message))
dispatch(CustomLabware.customLabwareListFailure(error.message, source))
})
}

Expand All @@ -54,7 +58,9 @@ const overwriteLabware = (
const dir = getFullConfig().labware.directory
return Definitions.addLabwareFile(next.filename, dir)
})
.then(() => fetchAndValidateCustomLabware(dispatch))
.then(() =>
fetchAndValidateCustomLabware(dispatch, CustomLabware.OVERWRITE_LABWARE)
)
}

const copyLabware = (
Expand All @@ -74,21 +80,25 @@ const copyLabware = (
}

return Definitions.addLabwareFile(next.filename, dir).then(() =>
fetchAndValidateCustomLabware(dispatch)
fetchAndValidateCustomLabware(dispatch, CustomLabware.ADD_LABWARE)
)
})
}

export function registerLabware(dispatch: Dispatch, mainWindow: {}) {
handleConfigChange('labware.directory', () => {
fetchAndValidateCustomLabware(dispatch)
fetchAndValidateCustomLabware(dispatch, CustomLabware.CHANGE_DIRECTORY)
})

return function handleActionForLabware(action: Action) {
switch (action.type) {
case CustomLabware.FETCH_CUSTOM_LABWARE:
case 'shell:CHECK_UPDATE': {
fetchAndValidateCustomLabware(dispatch)
const source =
action.type === CustomLabware.FETCH_CUSTOM_LABWARE
? CustomLabware.POLL
: CustomLabware.INITIAL
fetchAndValidateCustomLabware(dispatch, source)
break
}

Expand Down
114 changes: 114 additions & 0 deletions app/src/analytics/__tests__/custom-labware-events.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// @flow

import { makeEvent } from '../make-event'

import * as CustomLabware from '../../custom-labware'
import * as LabwareFixtures from '../../custom-labware/__fixtures__'

import type { State, Action } from '../../types'
import type { AnalyticsEvent } from '../types'

type EventSpec = {|
name: string,
action: Action,
expected: AnalyticsEvent,
|}

const SPECS: Array<EventSpec> = [
{
name: 'addCustomLabware success',
action: CustomLabware.customLabwareList([], CustomLabware.ADD_LABWARE),
expected: {
name: 'addCustomLabware',
properties: { success: true, overwrite: false, error: '' },
superProperties: { customLabwareCount: 0 },
},
},
{
name: 'addCustomLabware overwrite success',
action: CustomLabware.customLabwareList(
[],
CustomLabware.OVERWRITE_LABWARE
),
expected: {
name: 'addCustomLabware',
properties: { success: true, overwrite: true, error: '' },
superProperties: { customLabwareCount: 0 },
},
},
{
name: 'addCustomLabware failure with bad labware',
action: CustomLabware.addCustomLabwareFailure(
LabwareFixtures.mockInvalidLabware
),
expected: {
name: 'addCustomLabware',
properties: {
success: false,
overwrite: false,
error: 'INVALID_LABWARE_FILE',
},
},
},
{
name: 'addCustomLabware failure with system error',
action: CustomLabware.addCustomLabwareFailure(null, 'AH'),
expected: {
name: 'addCustomLabware',
properties: { success: false, overwrite: false, error: 'AH' },
},
},
{
name: 'changeLabwareSourceDirectory success',
action: CustomLabware.customLabwareList([], CustomLabware.CHANGE_DIRECTORY),
expected: {
name: 'changeLabwareSourceDirectory',
properties: { success: true, error: '' },
superProperties: { customLabwareCount: 0 },
},
},
{
name: 'changeLabwareSourceDirectory failure',
action: CustomLabware.customLabwareListFailure(
'AH',
CustomLabware.CHANGE_DIRECTORY
),
expected: {
name: 'changeLabwareSourceDirectory',
properties: { success: false, error: 'AH' },
},
},
{
name: 'customLabwareListError on application boot',
action: CustomLabware.customLabwareListFailure('AH', CustomLabware.INITIAL),
expected: {
name: 'customLabwareListError',
properties: { error: 'AH' },
},
},
{
name: 'labware:CUSTOM_LABWARE_LIST sets labware count super property',
action: CustomLabware.customLabwareList(
[
LabwareFixtures.mockValidLabware,
LabwareFixtures.mockValidLabware,
LabwareFixtures.mockInvalidLabware,
],
CustomLabware.INITIAL
),
expected: expect.objectContaining({
superProperties: { customLabwareCount: 2 },
}),
},
]

const MOCK_STATE: State = ({ mockState: true }: any)

describe('custom labware analytics events', () => {
SPECS.forEach(spec => {
const { name, action, expected } = spec
test(name, () => {
return expect(makeEvent(action, MOCK_STATE)).resolves.toEqual(expected)
})
})
})
2 changes: 1 addition & 1 deletion app/src/analytics/__tests__/epics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { TestScheduler } from 'rxjs/testing'

import { trackEvent, setMixpanelTracking } from '../mixpanel'
import makeEvent from '../make-event'
import { makeEvent } from '../make-event'
import * as epics from '../epics'

jest.mock('../make-event')
Expand Down
2 changes: 1 addition & 1 deletion app/src/analytics/__tests__/make-event.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// events map tests
import makeEvent from '../make-event'
import { makeEvent } from '../make-event'
import {
actions as robotActions,
selectors as robotSelectors,
Expand Down
2 changes: 1 addition & 1 deletion app/src/analytics/epics.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from 'rxjs/operators'

import { setMixpanelTracking, trackEvent } from './mixpanel'
import makeEvent from './make-event'
import { makeEvent } from './make-event'

import type { State, Action, Epic } from '../types'
import type { TrackEventArgs, AnalyticsConfig } from './types'
Expand Down
74 changes: 73 additions & 1 deletion app/src/analytics/make-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import createLogger from '../logger'
import { selectors as robotSelectors } from '../robot'
import { getConnectedRobot } from '../discovery'
import * as CustomLabware from '../custom-labware'
import * as brActions from '../shell/buildroot/actions'
import {
getProtocolAnalyticsData,
Expand All @@ -15,7 +16,7 @@ import type { AnalyticsEvent } from './types'

const log = createLogger(__filename)

export default function makeEvent(
export function makeEvent(
action: Action,
state: State
): Promise<AnalyticsEvent | null> {
Expand Down Expand Up @@ -180,6 +181,77 @@ export default function makeEvent(
properties: { ...data },
})
}

case CustomLabware.CUSTOM_LABWARE_LIST: {
const { payload: labware, meta } = action
const { source } = meta
const customLabwareCount = labware.filter(
lw => lw.type === CustomLabware.VALID_LABWARE_FILE
).length
const superProperties = { customLabwareCount }

if (
source === CustomLabware.ADD_LABWARE ||
source === CustomLabware.OVERWRITE_LABWARE
) {
return Promise.resolve({
name: 'addCustomLabware',
properties: {
success: true,
overwrite: source === CustomLabware.OVERWRITE_LABWARE,
error: '',
},
superProperties,
})
}

if (source === CustomLabware.CHANGE_DIRECTORY) {
return Promise.resolve({
name: 'changeLabwareSourceDirectory',
properties: { success: true, error: '' },
superProperties,
})
}

return Promise.resolve({ superProperties })
}

case CustomLabware.CUSTOM_LABWARE_LIST_FAILURE: {
const { message: error } = action.payload
const { source } = action.meta

if (source === CustomLabware.CHANGE_DIRECTORY) {
return Promise.resolve({
name: 'changeLabwareSourceDirectory',
properties: { success: false, error },
})
}

if (source === CustomLabware.INITIAL) {
return Promise.resolve({
name: 'customLabwareListError',
properties: { error },
})
}

break
}

case CustomLabware.ADD_CUSTOM_LABWARE_FAILURE: {
const { labware, message } = action.payload
let error = ''

if (labware !== null) {
error = labware.type
} else if (message !== null) {
error = message
}

return Promise.resolve({
name: 'addCustomLabware',
properties: { success: false, overwrite: false, error },
})
}
}

return Promise.resolve(null)
Expand Down
5 changes: 4 additions & 1 deletion app/src/analytics/mixpanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ export function trackEvent(event: AnalyticsEvent, config: AnalyticsConfig) {
const { optedIn } = config

log.debug('Trackable event', { event, optedIn })
if (MIXPANEL_ID && optedIn) mixpanel.track(event.name, event.properties)
if (MIXPANEL_ID && optedIn) {
if (event.superProperties) mixpanel.register(event.superProperties)
if (event.name) mixpanel.track(event.name, event.properties)
}
}

export function setMixpanelTracking(config: AnalyticsConfig) {
Expand Down
11 changes: 7 additions & 4 deletions app/src/analytics/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ export type BuildrootAnalyticsData = {|
error: string | null,
|}

export type AnalyticsEvent = {|
name: string,
properties: {},
|}
export type AnalyticsEvent =
| {|
name: string,
properties: {},
superProperties?: {},
|}
| {| superProperties: {} |}

export type TrackEventArgs = [AnalyticsEvent, AnalyticsConfig]
Loading

0 comments on commit b4fd536

Please sign in to comment.