Track GA for safe actions (#1302)

* Track GA for safe actions

* Add tracking for safeListSidebar

* review changes

* review changes v2
This commit is contained in:
nicolas 2020-08-31 11:33:55 -03:00 committed by GitHub
parent e9f7acff63
commit f7d4cfe112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 265 additions and 191 deletions

View File

@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react'
import Drawer from '@material-ui/core/Drawer'
import SearchIcon from '@material-ui/icons/Search'
import SearchBar from 'material-ui-search-bar'
import * as React from 'react'
import { connect } from 'react-redux'
import SafeList from './SafeList'
@ -16,12 +16,11 @@ import Link from 'src/components/layout/Link'
import Row from 'src/components/layout/Row'
import { WELCOME_ADDRESS } from 'src/routes/routes'
import setDefaultSafe from 'src/logic/safe/store/actions/setDefaultSafe'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { defaultSafeSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { AppReduxState } from 'src/store'
const { useEffect, useMemo, useState } = React
export const SafeListSidebarContext = React.createContext({
isOpen: false,
toggleSidebar: () => {},
@ -39,12 +38,7 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
const [isOpen, setIsOpen] = useState(false)
const [filter, setFilter] = useState('')
const classes = useSidebarStyles()
useEffect(() => {
setTimeout(() => {
setFilter('')
}, 300)
}, [isOpen])
const { trackEvent } = useAnalytics()
const searchClasses = {
input: classes.searchInput,
@ -54,6 +48,9 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
}
const toggleSidebar = () => {
if (!isOpen) {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Safe List Sidebar' })
}
setIsOpen((prevIsOpen) => !prevIsOpen)
}
@ -73,6 +70,12 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
const filteredSafes = useMemo(() => filterBy(filter, safes), [safes, filter])
useEffect(() => {
setTimeout(() => {
setFilter('')
}, 300)
}, [isOpen])
return (
<SafeListSidebarContext.Provider value={{ isOpen, toggleSidebar }}>
<Drawer

View File

@ -41,6 +41,7 @@ import RemoveOwnerIcon from 'src/routes/safe/components/Settings/assets/icons/bi
import RemoveOwnerIconDisabled from 'src/routes/safe/components/Settings/assets/icons/disabled-bin.svg'
import { addressBookQueryParamsSelector, safesListSelector } from 'src/logic/safe/store/selectors'
import { checksumAddress } from 'src/utils/checksumAddress'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const AddressBookTable = ({ classes }) => {
const columns = generateColumns()
@ -53,6 +54,11 @@ const AddressBookTable = ({ classes }) => {
const [editCreateEntryModalOpen, setEditCreateEntryModalOpen] = useState(false)
const [deleteEntryModalOpen, setDeleteEntryModalOpen] = useState(false)
const [sendFundsModalOpen, setSendFundsModalOpen] = useState(false)
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'AddressBook' })
}, [trackEvent])
useEffect(() => {
if (entryAddressToEditOrCreateNew) {

View File

@ -19,6 +19,7 @@ import {
import { isSameURL } from 'src/utils/url'
import { useIframeMessageHandler } from './hooks/useIframeMessageHandler'
import ConfirmTransactionModal from './components/ConfirmTransactionModal'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const centerCSS = css`
display: flex;
@ -64,6 +65,7 @@ const Apps = (): React.ReactElement => {
)
const iframeRef = useRef<HTMLIFrameElement>()
const { trackEvent } = useAnalytics()
const granted = useSelector(grantedSelector)
const safeAddress = useSelector(safeParamAddressFromStateSelector)
const safeName = useSelector(safeNameSelector)
@ -111,6 +113,7 @@ const Apps = (): React.ReactElement => {
[selectedAppId],
)
// Auto Select app first App
useEffect(() => {
const selectFirstEnabledApp = () => {
const firstEnabledApp = appList.find((a) => !a.disabled)
@ -124,7 +127,14 @@ const Apps = (): React.ReactElement => {
if (initialSelect || currentAppWasDisabled) {
selectFirstEnabledApp()
}
}, [appList, selectedApp, selectedAppId])
}, [appList, selectedApp, selectedAppId, trackEvent])
// track GA
useEffect(() => {
if (selectedApp) {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Apps', label: selectedApp.name })
}
}, [selectedApp, trackEvent])
const handleIframeLoad = useCallback(() => {
const iframe = iframeRef.current

View File

@ -1,9 +1,9 @@
import React, { useEffect } from 'react'
import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer'
import TableRow from '@material-ui/core/TableRow'
import { makeStyles } from '@material-ui/core/styles'
import { List } from 'immutable'
import React from 'react'
import { useSelector } from 'react-redux'
import { styles } from './styles'
@ -29,6 +29,7 @@ import {
} from 'src/routes/safe/components/Balances/dataFetcher'
import { extendedSafeTokensSelector, grantedSelector } from 'src/routes/safe/container/selector'
import { Skeleton } from '@material-ui/lab'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const useStyles = makeStyles(styles as any)
@ -61,6 +62,11 @@ const Coins = (props: Props): React.ReactElement => {
const currencyValues = useSelector(safeFiatBalancesListSelector)
const granted = useSelector(grantedSelector)
const [filteredData, setFilteredData] = React.useState<List<BalanceData>>(List())
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Coins' })
}, [trackEvent])
React.useMemo(() => {
setFilteredData(getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate))

View File

@ -1,6 +1,6 @@
import React, { useEffect } from 'react'
import Card from '@material-ui/core/Card'
import { makeStyles } from '@material-ui/core/styles'
import React from 'react'
import { useSelector } from 'react-redux'
import Item from './components/Item'
@ -10,6 +10,7 @@ import { activeNftAssetsListSelector, nftTokensSelector } from 'src/logic/collec
import SendModal from 'src/routes/safe/components/Balances/SendModal'
import { safeSelector } from 'src/logic/safe/store/selectors'
import { fontColor, lg, screenSm, screenXs } from 'src/theme/variables'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const useStyles = makeStyles({
cardInner: {
@ -74,13 +75,18 @@ const useStyles = makeStyles({
},
} as any)
const Collectibles = () => {
const Collectibles = (): React.ReactElement => {
const classes = useStyles()
const [selectedToken, setSelectedToken] = React.useState({})
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
const { address, ethBalance, name } = useSelector(safeSelector)
const nftTokens = useSelector(nftTokensSelector)
const activeAssetsList = useSelector(activeNftAssetsListSelector)
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Collectibles' })
}, [trackEvent])
const handleItemSend = (nftToken) => {
setSelectedToken(nftToken)

View File

@ -1,6 +1,6 @@
import { Loader, Text, theme, Title } from '@gnosis.pm/safe-react-components'
import { makeStyles } from '@material-ui/core/styles'
import React from 'react'
import React, { useEffect } from 'react'
import { useSelector } from 'react-redux'
import styled from 'styled-components'
@ -10,6 +10,7 @@ import ModulesTable from './ModulesTable'
import Block from 'src/components/layout/Block'
import { safeModulesSelector, safeNonceSelector } from 'src/logic/safe/store/selectors'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const useStyles = makeStyles(styles)
@ -39,10 +40,14 @@ const LoadingModules = (): React.ReactElement => {
const Advanced = (): React.ReactElement => {
const classes = useStyles()
const nonce = useSelector(safeNonceSelector)
const modules = useSelector(safeModulesSelector)
const moduleData = getModuleData(modules) ?? null
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Advanced' })
}, [trackEvent])
return (
<>

View File

@ -33,7 +33,7 @@ export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
const useStyles = makeStyles(styles)
type OwnProps = {
isOpen: true
isOpen: boolean
onClose: () => void
ownerAddress: string
selectedOwnerName: string

View File

@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react'
import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer'
import TableRow from '@material-ui/core/TableRow'
import { withStyles } from '@material-ui/core/styles'
import { makeStyles } from '@material-ui/core/styles'
import cn from 'classnames'
import React from 'react'
import { List } from 'immutable'
import RemoveOwnerIcon from '../assets/icons/bin.svg'
@ -28,6 +29,9 @@ import Img from 'src/components/layout/Img'
import Paragraph from 'src/components/layout/Paragraph/index'
import Row from 'src/components/layout/Row'
import { getOwnersWithNameFromAddressBook } from 'src/logic/addressBook/utils'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { AddressBookEntryProps } from 'src/logic/addressBook/model/addressBook'
import { SafeOwner } from 'src/logic/safe/store/models/safe'
export const RENAME_OWNER_BTN_TEST_ID = 'rename-owner-btn'
export const REMOVE_OWNER_BTN_TEST_ID = 'remove-owner-btn'
@ -35,49 +39,52 @@ export const ADD_OWNER_BTN_TEST_ID = 'add-owner-btn'
export const REPLACE_OWNER_BTN_TEST_ID = 'replace-owner-btn'
export const OWNERS_ROW_TEST_ID = 'owners-row'
class ManageOwners extends React.Component<any, any> {
constructor(props) {
super(props)
const useStyles = makeStyles(styles)
this.state = {
selectedOwnerAddress: undefined,
selectedOwnerName: undefined,
type Props = {
addressBook: unknown
granted: boolean
owners: List<SafeOwner>
}
const ManageOwners = ({ addressBook, granted, owners }: Props): React.ReactElement => {
const { trackEvent } = useAnalytics()
const classes = useStyles()
const [selectedOwnerAddress, setSelectedOwnerAddress] = useState()
const [selectedOwnerName, setSelectedOwnerName] = useState()
const [modalsStatus, setModalStatus] = useState({
showAddOwner: false,
showRemoveOwner: false,
showReplaceOwner: false,
showEditOwner: false,
}
}
onShow = (action, row?: any) => () => {
this.setState({
[`show${action}`]: true,
selectedOwnerAddress: row && row.address,
selectedOwnerName: row && row.name,
})
const onShow = (action, row?: any) => () => {
setModalStatus((prevState) => ({
...prevState,
[`show${action}`]: !prevState[`show${action}`],
}))
setSelectedOwnerAddress(row && row.address)
setSelectedOwnerName(row && row.name)
}
onHide = (action) => () => {
this.setState({
[`show${action}`]: false,
selectedOwnerAddress: undefined,
selectedOwnerName: undefined,
})
const onHide = (action) => () => {
setModalStatus((prevState) => ({
...prevState,
[`show${action}`]: !Boolean(prevState[`show${action}`]),
}))
setSelectedOwnerAddress(undefined)
setSelectedOwnerName(undefined)
}
render() {
const { addressBook, classes, granted, owners } = this.props
const {
selectedOwnerAddress,
selectedOwnerName,
showAddOwner,
showEditOwner,
showRemoveOwner,
showReplaceOwner,
} = this.state
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Owners' })
}, [trackEvent])
const columns = generateColumns()
const autoColumns = columns.filter((c) => !c.custom)
const ownersAdbk = getOwnersWithNameFromAddressBook(addressBook, owners)
const ownersAdbk = getOwnersWithNameFromAddressBook(addressBook as AddressBookEntryProps, owners)
const ownerData = getOwnerData(ownersAdbk)
return (
@ -87,8 +94,8 @@ class ManageOwners extends React.Component<any, any> {
Manage Safe Owners
</Heading>
<Paragraph className={classes.annotation}>
Add, remove and replace owners or rename existing owners. Owner names are only stored locally and never
shared with Gnosis or any third parties.
Add, remove and replace owners or rename existing owners. Owner names are only stored locally and never shared
with Gnosis or any third parties.
</Paragraph>
<TableContainer>
<Table
@ -107,7 +114,6 @@ class ManageOwners extends React.Component<any, any> {
className={cn(classes.hide, index >= 3 && index === sortedData.size - 1 && classes.noBorderBottom)}
data-testid={OWNERS_ROW_TEST_ID}
key={index}
tabIndex={-1}
>
{autoColumns.map((column: any) => (
<TableCell align={column.align} component="td" key={column.id} style={cellWidth(column.width)}>
@ -123,7 +129,7 @@ class ManageOwners extends React.Component<any, any> {
<Img
alt="Edit owner"
className={classes.editOwnerIcon}
onClick={this.onShow('EditOwner', row)}
onClick={onShow('EditOwner', row)}
src={RenameOwnerIcon}
testId={RENAME_OWNER_BTN_TEST_ID}
/>
@ -132,7 +138,7 @@ class ManageOwners extends React.Component<any, any> {
<Img
alt="Replace owner"
className={classes.replaceOwnerIcon}
onClick={this.onShow('ReplaceOwner', row)}
onClick={onShow('ReplaceOwner', row)}
src={ReplaceOwnerIcon}
testId={REPLACE_OWNER_BTN_TEST_ID}
/>
@ -140,7 +146,7 @@ class ManageOwners extends React.Component<any, any> {
<Img
alt="Remove owner"
className={classes.removeOwnerIcon}
onClick={this.onShow('RemoveOwner', row)}
onClick={onShow('RemoveOwner', row)}
src={RemoveOwnerIcon}
testId={REMOVE_OWNER_BTN_TEST_ID}
/>
@ -162,7 +168,7 @@ class ManageOwners extends React.Component<any, any> {
<Col end="xs">
<Button
color="primary"
onClick={this.onShow('AddOwner')}
onClick={onShow('AddOwner')}
size="small"
testId={ADD_OWNER_BTN_TEST_ID}
variant="contained"
@ -173,28 +179,27 @@ class ManageOwners extends React.Component<any, any> {
</Row>
</>
)}
<AddOwnerModal isOpen={showAddOwner} onClose={this.onHide('AddOwner')} />
<AddOwnerModal isOpen={modalsStatus.showAddOwner} onClose={onHide('AddOwner')} />
<RemoveOwnerModal
isOpen={showRemoveOwner}
onClose={this.onHide('RemoveOwner')}
isOpen={modalsStatus.showRemoveOwner}
onClose={onHide('RemoveOwner')}
ownerAddress={selectedOwnerAddress}
ownerName={selectedOwnerName}
/>
<ReplaceOwnerModal
isOpen={showReplaceOwner}
onClose={this.onHide('ReplaceOwner')}
isOpen={modalsStatus.showReplaceOwner}
onClose={onHide('ReplaceOwner')}
ownerAddress={selectedOwnerAddress}
ownerName={selectedOwnerName}
/>
<EditOwnerModal
isOpen={showEditOwner}
onClose={this.onHide('EditOwner')}
isOpen={modalsStatus.showEditOwner}
onClose={onHide('EditOwner')}
ownerAddress={selectedOwnerAddress}
selectedOwnerName={selectedOwnerName}
/>
</>
)
}
}
export default withStyles(styles as any)(ManageOwners)
export default ManageOwners

View File

@ -1,6 +1,7 @@
import { lg, sm } from 'src/theme/variables'
import { createStyles } from '@material-ui/core'
export const styles = () => ({
export const styles = createStyles({
formContainer: {
minHeight: '420px',
},

View File

@ -1,5 +1,5 @@
import { makeStyles } from '@material-ui/core/styles'
import React from 'react'
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { styles } from './style'
@ -29,6 +29,7 @@ import {
safeNeedsUpdateSelector,
safeParamAddressFromStateSelector,
} from 'src/logic/safe/store/selectors'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn'
@ -44,6 +45,7 @@ const SafeDetails = (): React.ReactElement => {
const safeName = useSelector(safeNameSelector)
const safeNeedsUpdate = useSelector(safeNeedsUpdateSelector)
const safeCurrentVersion = useSelector(safeCurrentVersionSelector)
const { trackEvent } = useAnalytics()
const [isModalOpen, setModalOpen] = React.useState(false)
const safeAddress = useSelector(safeParamAddressFromStateSelector)
@ -63,6 +65,10 @@ const SafeDetails = (): React.ReactElement => {
setModalOpen(true)
}
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Details' })
}, [trackEvent])
return (
<>
<GnoForm onSubmit={handleSubmit}>

View File

@ -1,6 +1,6 @@
import { withStyles } from '@material-ui/core/styles'
import { withSnackbar } from 'notistack'
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import ChangeThreshold from './ChangeThreshold'
@ -22,6 +22,7 @@ import {
safeParamAddressFromStateSelector,
safeThresholdSelector,
} from 'src/logic/safe/store/selectors'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
const ThresholdSettings = ({ classes, closeSnackbar, enqueueSnackbar }) => {
const [isModalOpen, setModalOpen] = useState(false)
@ -52,6 +53,12 @@ const ThresholdSettings = ({ classes, closeSnackbar, enqueueSnackbar }) => {
)
}
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Owners' })
}, [trackEvent])
return (
<>
<Block className={classes.container}>

View File

@ -7,7 +7,7 @@ import { withStyles } from '@material-ui/core/styles'
import ExpandLess from '@material-ui/icons/ExpandLess'
import ExpandMore from '@material-ui/icons/ExpandMore'
import cn from 'classnames'
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import { useSelector } from 'react-redux'
import ExpandedTxComponent from './ExpandedTx'
@ -21,6 +21,7 @@ import Block from 'src/components/layout/Block'
import Row from 'src/components/layout/Row'
import { safeCancellationTransactionsSelector } from 'src/logic/safe/store/selectors'
import { extendedTransactionsSelector } from 'src/logic/safe/store/selectors/transactions'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
export const TRANSACTION_ROW_TEST_ID = 'transaction-row'
@ -28,6 +29,11 @@ const TxsTable = ({ classes }) => {
const [expandedTx, setExpandedTx] = useState(null)
const cancellationTransactions = useSelector(safeCancellationTransactionsSelector)
const transactions = useSelector(extendedTransactionsSelector)
const { trackEvent } = useAnalytics()
useEffect(() => {
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Transactions' })
}, [trackEvent])
const handleTxExpand = (safeTxHash) => {
setExpandedTx((prevTx) => (prevTx === safeTxHash ? null : safeTxHash))

View File

@ -1,12 +1,14 @@
import { useCallback, useEffect, useState } from 'react'
import GoogleAnalytics from 'react-ga'
import GoogleAnalytics, { EventArgs } from 'react-ga'
import { getGoogleAnalyticsTrackingID } from 'src/config'
import { COOKIES_KEY } from 'src/logic/cookies/model/cookie'
import { loadFromCookie } from 'src/logic/cookies/utils'
export const SAFE_NAVIGATION_EVENT = 'Safe Navigation'
let analyticsLoaded = false
export const loadGoogleAnalytics = () => {
export const loadGoogleAnalytics = (): void => {
if (analyticsLoaded) {
return
}
@ -22,7 +24,12 @@ export const loadGoogleAnalytics = () => {
}
}
export const useAnalytics = () => {
type UseAnalyticsResponse = {
trackPage: (path: string) => void
trackEvent: (event: EventArgs) => void
}
export const useAnalytics = (): UseAnalyticsResponse => {
const [analyticsAllowed, setAnalyticsAllowed] = useState(false)
useEffect(() => {
@ -37,18 +44,24 @@ export const useAnalytics = () => {
}, [])
const trackPage = useCallback(
(page, options = {}) => {
(page) => {
if (!analyticsAllowed || !analyticsLoaded) {
return
}
GoogleAnalytics.set({
page,
...options,
})
GoogleAnalytics.pageview(page)
},
[analyticsAllowed],
)
return { trackPage }
const trackEvent = useCallback(
(event: EventArgs) => {
if (!analyticsAllowed || !analyticsLoaded) {
return
}
GoogleAnalytics.event(event)
},
[analyticsAllowed],
)
return { trackPage, trackEvent }
}