Merge pull request #866 from 0xProject/feature/website/integrate-heap
Integrate Heap analytics into the website
This commit is contained in:
@@ -788,7 +788,7 @@ export class Blockchain {
|
||||
return tokenByAddress;
|
||||
}
|
||||
private async _onPageLoadInitFireAndForgetAsync(): Promise<void> {
|
||||
await utils.onPageLoadAsync(); // wait for page to load
|
||||
await utils.onPageLoadPromise; // wait for page to load
|
||||
const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync();
|
||||
this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET;
|
||||
const injectedWeb3IfExists = Blockchain._getInjectedWeb3();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getOrderHashHex, isValidSignature } from '@0xproject/order-utils';
|
||||
import { colors, constants as sharedConstants } from '@0xproject/react-shared';
|
||||
import { colors } from '@0xproject/react-shared';
|
||||
import { Order as ZeroExOrder } from '@0xproject/types';
|
||||
import { BigNumber, logUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
@@ -506,6 +506,10 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
|
||||
await this._checkForUntrackedTokensAndAskToAddAsync();
|
||||
}
|
||||
private _trackOrderEvent(eventName: string): void {
|
||||
const parsedOrder = this.state.parsedOrder;
|
||||
analytics.trackOrderEvent(eventName, parsedOrder);
|
||||
}
|
||||
private async _onFillOrderClickFireAndForgetAsync(): Promise<void> {
|
||||
if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) {
|
||||
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
|
||||
@@ -552,14 +556,12 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const eventLabel = `${parsedOrder.metadata.takerToken.symbol}-${networkName}`;
|
||||
try {
|
||||
const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync(
|
||||
signedOrder,
|
||||
this.props.orderFillAmount,
|
||||
);
|
||||
analytics.logEvent('Portal', 'Fill Order Success', eventLabel, parsedOrder.signedOrder.takerTokenAmount);
|
||||
this._trackOrderEvent('Fill Order Success');
|
||||
// After fill completes, let's force fetch the token balances
|
||||
this.props.dispatcher.forceTokenStateRefetch();
|
||||
this.setState({
|
||||
@@ -573,7 +575,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
this.setState({
|
||||
isFilling: false,
|
||||
});
|
||||
analytics.logEvent('Portal', 'Fill Order Failure', eventLabel, parsedOrder.signedOrder.takerTokenAmount);
|
||||
this._trackOrderEvent('Fill Order Failure');
|
||||
const errMsg = `${err}`;
|
||||
if (utils.didUserDenyWeb3Request(errMsg)) {
|
||||
return;
|
||||
@@ -628,8 +630,6 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const eventLabel = `${parsedOrder.metadata.makerToken.symbol}-${networkName}`;
|
||||
try {
|
||||
await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount);
|
||||
this.setState({
|
||||
@@ -638,7 +638,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
globalErrMsg: '',
|
||||
unavailableTakerAmount: takerTokenAmount,
|
||||
});
|
||||
analytics.logEvent('Portal', 'Cancel Order Success', eventLabel, parsedOrder.signedOrder.makerTokenAmount);
|
||||
this._trackOrderEvent('Cancel Order Success');
|
||||
return;
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
@@ -648,7 +648,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
if (utils.didUserDenyWeb3Request(errMsg)) {
|
||||
return;
|
||||
}
|
||||
analytics.logEvent('Portal', 'Cancel Order Failure', eventLabel, parsedOrder.signedOrder.makerTokenAmount);
|
||||
this._trackOrderEvent('Cancel Order Failure');
|
||||
globalErrMsg = 'Failed to cancel order, please refresh and try again';
|
||||
logUtils.log(`${err}`);
|
||||
this.setState({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button } from 'ts/components/ui/button';
|
||||
import { Container } from 'ts/components/ui/container';
|
||||
import { Input } from 'ts/components/ui/input';
|
||||
import { Text } from 'ts/components/ui/text';
|
||||
import { analytics } from 'ts/utils/analytics';
|
||||
import { backendClient } from 'ts/utils/backend_client';
|
||||
|
||||
export interface SubscribeFormProps {}
|
||||
@@ -112,6 +113,9 @@ export class SubscribeForm extends React.Component<SubscribeFormProps, Subscribe
|
||||
try {
|
||||
const response = await backendClient.subscribeToNewsletterAsync(this.state.emailText);
|
||||
const status = response.status === 200 ? SubscribeFormStatus.Success : SubscribeFormStatus.Error;
|
||||
if (status === SubscribeFormStatus.Success) {
|
||||
analytics.identify(this.state.emailText, 'email');
|
||||
}
|
||||
this.setState({ status, emailText: '' });
|
||||
} catch (error) {
|
||||
this._setStatus(SubscribeFormStatus.Error);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { generatePseudoRandomSalt, getOrderHashHex } from '@0xproject/order-utils';
|
||||
import { colors, constants as sharedConstants } from '@0xproject/react-shared';
|
||||
import { ECSignature, Order } from '@0xproject/types';
|
||||
import { colors } from '@0xproject/react-shared';
|
||||
import { ECSignature, Order as ZeroExOrder } from '@0xproject/types';
|
||||
import { BigNumber, logUtils } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
@@ -20,7 +20,7 @@ import { SwapIcon } from 'ts/components/ui/swap_icon';
|
||||
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||
import { portalOrderSchema } from 'ts/schemas/portal_order_schema';
|
||||
import { validator } from 'ts/schemas/validator';
|
||||
import { AlertTypes, BlockchainErrs, HashData, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types';
|
||||
import { AlertTypes, BlockchainErrs, HashData, Order, Side, SideToAssetToken, Token, TokenByAddress } from 'ts/types';
|
||||
import { analytics } from 'ts/utils/analytics';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import { errorReporter } from 'ts/utils/error_reporter';
|
||||
@@ -254,7 +254,8 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
userAddressIfExists,
|
||||
debitToken.address,
|
||||
);
|
||||
const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount;
|
||||
const receiveToken = this.props.sideToAssetToken[Side.Receive];
|
||||
const receiveAmount = receiveToken.amount;
|
||||
if (
|
||||
!_.isUndefined(debitToken.amount) &&
|
||||
!_.isUndefined(receiveAmount) &&
|
||||
@@ -264,24 +265,28 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
debitBalance.gte(debitToken.amount) &&
|
||||
debitAllowance.gte(debitToken.amount)
|
||||
) {
|
||||
const didSignSuccessfully = await this._signTransactionAsync();
|
||||
if (didSignSuccessfully) {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const eventLabel = `${this.props.tokenByAddress[debitToken.address].symbol}-${networkName}`;
|
||||
analytics.logEvent('Portal', 'Sign Order Success', eventLabel, debitToken.amount.toNumber());
|
||||
const signedOrder = await this._signTransactionAsync();
|
||||
const doesSignedOrderExist = !_.isUndefined(signedOrder);
|
||||
if (doesSignedOrderExist) {
|
||||
analytics.trackOrderEvent('Sign Order Success', signedOrder);
|
||||
this.setState({
|
||||
globalErrMsg: '',
|
||||
shouldShowIncompleteErrs: false,
|
||||
});
|
||||
}
|
||||
return didSignSuccessfully;
|
||||
return doesSignedOrderExist;
|
||||
} else {
|
||||
let globalErrMsg = 'You must fix the above errors in order to generate a valid order';
|
||||
if (this.props.userAddress === '') {
|
||||
globalErrMsg = 'You must enable wallet communication';
|
||||
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
|
||||
}
|
||||
analytics.logEvent('Portal', 'Sign Order Failure', globalErrMsg);
|
||||
analytics.track('Sign Order Failure', {
|
||||
makerTokenAmount: debitToken.amount.toString(),
|
||||
makerToken: this.props.tokenByAddress[debitToken.address].symbol,
|
||||
takerTokenAmount: receiveToken.amount.toString(),
|
||||
takerToken: this.props.tokenByAddress[receiveToken.address].symbol,
|
||||
});
|
||||
this.setState({
|
||||
globalErrMsg,
|
||||
shouldShowIncompleteErrs: true,
|
||||
@@ -289,7 +294,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private async _signTransactionAsync(): Promise<boolean> {
|
||||
private async _signTransactionAsync(): Promise<Order | undefined> {
|
||||
this.setState({
|
||||
signingState: SigningState.SIGNING,
|
||||
});
|
||||
@@ -299,11 +304,11 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
this.setState({
|
||||
signingState: SigningState.UNSIGNED,
|
||||
});
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
const hashData = this.props.hashData;
|
||||
|
||||
const zeroExOrder: Order = {
|
||||
const zeroExOrder: ZeroExOrder = {
|
||||
exchangeContractAddress: exchangeContractAddr,
|
||||
expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
|
||||
feeRecipient: hashData.feeRecipientAddress,
|
||||
@@ -320,9 +325,10 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
const orderHash = getOrderHashHex(zeroExOrder);
|
||||
|
||||
let globalErrMsg = '';
|
||||
let order;
|
||||
try {
|
||||
const ecSignature = await this.props.blockchain.signOrderHashAsync(orderHash);
|
||||
const order = utils.generateOrder(
|
||||
order = utils.generateOrder(
|
||||
exchangeContractAddr,
|
||||
this.props.sideToAssetToken,
|
||||
hashData.orderExpiryTimestamp,
|
||||
@@ -356,7 +362,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
|
||||
signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED,
|
||||
globalErrMsg,
|
||||
});
|
||||
return globalErrMsg === '';
|
||||
return order;
|
||||
}
|
||||
private _updateOrderAddress(address?: string): void {
|
||||
if (!_.isUndefined(address)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
|
||||
import { Styles } from '@0xproject/react-shared';
|
||||
import { BigNumber, logUtils } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import Toggle from 'material-ui/Toggle';
|
||||
@@ -111,14 +111,16 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
|
||||
if (!this._isAllowanceSet()) {
|
||||
newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
|
||||
}
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const eventLabel = `${this.props.token.symbol}-${networkName}`;
|
||||
const logData = {
|
||||
tokenSymbol: this.props.token.symbol,
|
||||
newAllowance: newAllowanceAmountInBaseUnits.toNumber(),
|
||||
};
|
||||
try {
|
||||
await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
|
||||
analytics.logEvent('Portal', 'Set Allowance Success', eventLabel, newAllowanceAmountInBaseUnits.toNumber());
|
||||
analytics.track('Set Allowances Success', logData);
|
||||
await this.props.refetchTokenStateAsync();
|
||||
} catch (err) {
|
||||
analytics.logEvent('Portal', 'Set Allowance Failure', eventLabel, newAllowanceAmountInBaseUnits.toNumber());
|
||||
analytics.track('Set Allowance Failure', logData);
|
||||
this.setState({
|
||||
isSpinnerVisible: false,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { constants as sharedConstants } from '@0xproject/react-shared';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
@@ -225,20 +224,21 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
|
||||
(this.props.stepIndex === 0 && !this.props.isRunning && this.props.blockchainIsLoaded) ||
|
||||
(!this.props.isRunning && !this.props.hasBeenClosed && this.props.blockchainIsLoaded)
|
||||
) {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
analytics.logEvent('Portal', 'Onboarding Started - Automatic', networkName, this.props.stepIndex);
|
||||
analytics.track('Onboarding Started', {
|
||||
reason: 'automatic',
|
||||
stepIndex: this.props.stepIndex,
|
||||
});
|
||||
this.props.updateIsRunning(true);
|
||||
}
|
||||
}
|
||||
private _updateOnboardingStep(stepIndex: number): void {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
this.props.updateOnboardingStep(stepIndex);
|
||||
analytics.logEvent('Portal', 'Update Onboarding Step', networkName, stepIndex);
|
||||
}
|
||||
private _closeOnboarding(): void {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
this.props.updateIsRunning(false);
|
||||
analytics.logEvent('Portal', 'Onboarding Closed', networkName, this.props.stepIndex);
|
||||
analytics.track('Onboarding Closed', {
|
||||
stepIndex: this.props.stepIndex,
|
||||
});
|
||||
}
|
||||
private _renderZrxAllowanceToggle(): React.ReactNode {
|
||||
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { colors, constants as sharedConstants } from '@0xproject/react-shared';
|
||||
import { colors } from '@0xproject/react-shared';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
@@ -388,10 +388,11 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
startOnboarding
|
||||
);
|
||||
}
|
||||
|
||||
private _startOnboarding(): void {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
analytics.logEvent('Portal', 'Onboarding Started - Manual', networkName, this.props.portalOnboardingStep);
|
||||
analytics.track('Onboarding Started', {
|
||||
reason: 'manual',
|
||||
stepIndex: this.props.portalOnboardingStep,
|
||||
});
|
||||
this.props.dispatcher.updatePortalOnboardingShowing(true);
|
||||
}
|
||||
private _renderWalletSection(): React.ReactNode {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
|
||||
import { Styles } from '@0xproject/react-shared';
|
||||
import * as _ from 'lodash';
|
||||
import { GridTile as PlainGridTile } from 'material-ui/GridList';
|
||||
import * as React from 'react';
|
||||
@@ -64,10 +64,10 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
|
||||
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
|
||||
const topTokens = props.relayerInfo.topTokens;
|
||||
const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume;
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[props.networkId];
|
||||
const eventLabel = `${props.relayerInfo.name}-${networkName}`;
|
||||
const onClick = () => {
|
||||
analytics.logEvent('Portal', 'Relayer Click', eventLabel);
|
||||
analytics.track('Relayer Click', {
|
||||
name: props.relayerInfo.name,
|
||||
});
|
||||
utils.openUrl(link);
|
||||
};
|
||||
const headerImageUrl = props.relayerInfo.logoImgUrl;
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
colors,
|
||||
constants as sharedConstants,
|
||||
EtherscanLinkSuffixes,
|
||||
utils as sharedUtils,
|
||||
} from '@0xproject/react-shared';
|
||||
import { colors, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -46,11 +41,11 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
|
||||
};
|
||||
}
|
||||
public render(): React.ReactNode {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`;
|
||||
const onClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
analytics.logEvent('Portal', 'Token Click', eventLabel);
|
||||
analytics.track('Token Click', {
|
||||
tokenSymbol: this.props.tokenInfo.symbol,
|
||||
});
|
||||
const tokenLink = this._tokenLinkFromToken(this.props.tokenInfo, this.props.networkId);
|
||||
utils.openUrl(tokenLink);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { constants as sharedConstants, EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
|
||||
import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
|
||||
import { BigNumber, errorUtils } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@@ -488,19 +488,17 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
);
|
||||
}
|
||||
private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const action =
|
||||
wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Opened' : 'Wallet - Unwrap WETH Opened';
|
||||
analytics.logEvent('Portal', action, networkName);
|
||||
analytics.track(action);
|
||||
this.setState({
|
||||
wrappedEtherDirection,
|
||||
});
|
||||
}
|
||||
private _closeWrappedEtherActionRow(wrappedEtherDirection: Side): void {
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const action =
|
||||
wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Closed' : 'Wallet - Unwrap WETH Closed';
|
||||
analytics.logEvent('Portal', action, networkName);
|
||||
analytics.track(action);
|
||||
this.setState({
|
||||
wrappedEtherDirection: undefined,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
|
||||
import { Styles } from '@0xproject/react-shared';
|
||||
import { BigNumber, logUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
@@ -188,20 +188,23 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
|
||||
this.setState({
|
||||
isEthConversionHappening: true,
|
||||
});
|
||||
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
|
||||
const etherToken = this.props.etherToken;
|
||||
const amountToConvert = this.state.currentInputAmount;
|
||||
const ethAmount = Web3Wrapper.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH).toString();
|
||||
const tokenAmount = Web3Wrapper.toUnitAmount(amountToConvert, etherToken.decimals).toString();
|
||||
try {
|
||||
const etherToken = this.props.etherToken;
|
||||
const amountToConvert = this.state.currentInputAmount;
|
||||
if (this.props.direction === Side.Deposit) {
|
||||
await this.props.blockchain.convertEthToWrappedEthTokensAsync(etherToken.address, amountToConvert);
|
||||
const ethAmount = Web3Wrapper.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH);
|
||||
this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
|
||||
analytics.logEvent('Portal', 'Wrap ETH Successfully', networkName);
|
||||
this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount} ETH to WETH`);
|
||||
analytics.track('Wrap ETH Success', {
|
||||
amount: ethAmount,
|
||||
});
|
||||
} else {
|
||||
await this.props.blockchain.convertWrappedEthTokensToEthAsync(etherToken.address, amountToConvert);
|
||||
const tokenAmount = Web3Wrapper.toUnitAmount(amountToConvert, etherToken.decimals);
|
||||
this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
|
||||
analytics.logEvent('Portal', 'Unwrap WETH Successfully', networkName);
|
||||
this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount} WETH to ETH`);
|
||||
analytics.track('Unwrap WETH Success', {
|
||||
amount: tokenAmount,
|
||||
});
|
||||
}
|
||||
await this.props.refetchEthTokenStateAsync();
|
||||
this.props.onConversionSuccessful();
|
||||
@@ -214,10 +217,14 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
|
||||
logUtils.log(err.stack);
|
||||
if (this.props.direction === Side.Deposit) {
|
||||
this.props.dispatcher.showFlashMessage('Failed to wrap your ETH. Please try again.');
|
||||
analytics.logEvent('Portal', 'Wrap ETH Failed', networkName);
|
||||
analytics.track('Wrap ETH Failure', {
|
||||
amount: ethAmount,
|
||||
});
|
||||
} else {
|
||||
this.props.dispatcher.showFlashMessage('Failed to unwrap your WETH. Please try again.');
|
||||
analytics.logEvent('Portal', 'Unwrap WETH Failed', networkName);
|
||||
analytics.track('Unwrap WETH Failed', {
|
||||
amount: tokenAmount,
|
||||
});
|
||||
}
|
||||
await errorReporter.reportAsync(err);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
|
||||
import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage';
|
||||
import { store } from 'ts/redux/store';
|
||||
import { WebsiteLegacyPaths, WebsitePaths } from 'ts/types';
|
||||
import { analytics } from 'ts/utils/analytics';
|
||||
import { muiTheme } from 'ts/utils/mui_theme';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
// Polyfills
|
||||
@@ -68,10 +67,6 @@ const LazyEthereumTypesDocumentation = createLazyComponent('Documentation', asyn
|
||||
System.import<any>(/* webpackChunkName: "ethereumTypesDocs" */ 'ts/containers/ethereum_types_documentation'),
|
||||
);
|
||||
|
||||
analytics.init();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
analytics.logProviderAsync((window as any).web3);
|
||||
|
||||
render(
|
||||
<Router>
|
||||
<div>
|
||||
|
||||
@@ -205,7 +205,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> {
|
||||
articlesBySection,
|
||||
},
|
||||
async () => {
|
||||
await utils.onPageLoadAsync();
|
||||
await utils.onPageLoadPromise;
|
||||
const hash = this.props.location.hash.slice(1);
|
||||
sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID);
|
||||
},
|
||||
|
||||
41
packages/website/ts/redux/analyticsMiddleware.ts
Normal file
41
packages/website/ts/redux/analyticsMiddleware.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Middleware } from 'redux';
|
||||
import { State } from 'ts/redux/reducer';
|
||||
import { ActionTypes } from 'ts/types';
|
||||
import { analytics } from 'ts/utils/analytics';
|
||||
|
||||
export const analyticsMiddleware: Middleware = store => next => action => {
|
||||
const nextAction = next(action);
|
||||
const nextState = (store.getState() as any) as State;
|
||||
switch (action.type) {
|
||||
case ActionTypes.UpdateInjectedProviderName:
|
||||
analytics.addEventProperties({
|
||||
injectedProviderName: nextState.injectedProviderName,
|
||||
});
|
||||
break;
|
||||
case ActionTypes.UpdateNetworkId:
|
||||
analytics.addEventProperties({
|
||||
networkId: nextState.networkId,
|
||||
});
|
||||
break;
|
||||
case ActionTypes.UpdateUserAddress:
|
||||
analytics.addUserProperties({
|
||||
ethAddress: nextState.userAddress,
|
||||
});
|
||||
break;
|
||||
case ActionTypes.UpdateUserEtherBalance:
|
||||
if (nextState.userEtherBalanceInWei) {
|
||||
analytics.addUserProperties({
|
||||
ethBalance: nextState.userEtherBalanceInWei.toString(),
|
||||
});
|
||||
}
|
||||
break;
|
||||
case ActionTypes.UpdatePortalOnboardingStep:
|
||||
analytics.track('Update Onboarding Step', {
|
||||
stepIndex: nextState.portalOnboardingStep,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return nextAction;
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as _ from 'lodash';
|
||||
import { createStore, Store as ReduxStore } from 'redux';
|
||||
import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly';
|
||||
import { applyMiddleware, createStore, Store as ReduxStore } from 'redux';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
|
||||
import { stateStorage } from 'ts/local_storage/state_storage';
|
||||
import { analyticsMiddleware } from 'ts/redux/analyticsMiddleware';
|
||||
import { reducer, State } from 'ts/redux/reducer';
|
||||
|
||||
const ONE_SECOND = 1000;
|
||||
@@ -9,7 +10,7 @@ const ONE_SECOND = 1000;
|
||||
export const store: ReduxStore<State> = createStore(
|
||||
reducer,
|
||||
stateStorage.getPersistedDefaultState(),
|
||||
devToolsEnhancer({ name: '0x Website Redux Store' }),
|
||||
composeWithDevTools(applyMiddleware(analyticsMiddleware)),
|
||||
);
|
||||
store.subscribe(
|
||||
_.throttle(() => {
|
||||
|
||||
@@ -516,8 +516,10 @@ export interface OutdatedWrappedEtherByNetworkId {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ItemByAddress<T> {
|
||||
[address: string]: T;
|
||||
export type ItemByAddress<T> = ObjectMap<T>;
|
||||
|
||||
export interface ObjectMap<T> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
export type TokenStateByAddress = ItemByAddress<TokenState>;
|
||||
|
||||
@@ -1,27 +1,83 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as ReactGA from 'react-ga';
|
||||
import { InjectedWeb3 } from 'ts/types';
|
||||
import { configs } from 'ts/utils/configs';
|
||||
import { ObjectMap, Order } from 'ts/types';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
|
||||
export const analytics = {
|
||||
init(): void {
|
||||
ReactGA.initialize(configs.GOOGLE_ANALYTICS_ID);
|
||||
},
|
||||
logEvent(category: string, action: string, label: string, value?: any): void {
|
||||
ReactGA.event({
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
value,
|
||||
});
|
||||
},
|
||||
async logProviderAsync(web3IfExists: InjectedWeb3): Promise<void> {
|
||||
await utils.onPageLoadAsync();
|
||||
const providerType =
|
||||
!_.isUndefined(web3IfExists) && !_.isUndefined(web3IfExists.currentProvider)
|
||||
? utils.getProviderType(web3IfExists.currentProvider)
|
||||
: 'NONE';
|
||||
ReactGA.ga('set', 'dimension1', providerType);
|
||||
},
|
||||
};
|
||||
export interface HeapAnalytics {
|
||||
loaded: boolean;
|
||||
identify(id: string, idType: string): void;
|
||||
track(eventName: string, eventProperties?: ObjectMap<string | number>): void;
|
||||
resetIdentity(): void;
|
||||
addUserProperties(properties: ObjectMap<string | number>): void;
|
||||
addEventProperties(properties: ObjectMap<string | number>): void;
|
||||
removeEventProperty(property: string): void;
|
||||
clearEventProperties(): void;
|
||||
}
|
||||
export class Analytics {
|
||||
private _heap: HeapAnalytics;
|
||||
public static init(): Analytics {
|
||||
return new Analytics(Analytics.getHeap());
|
||||
}
|
||||
public static getHeap(): HeapAnalytics {
|
||||
const heap = (window as any).heap;
|
||||
if (!_.isUndefined(heap)) {
|
||||
return heap;
|
||||
} else {
|
||||
throw new Error('Could not find the Heap SDK on the page.');
|
||||
}
|
||||
}
|
||||
constructor(heap: HeapAnalytics) {
|
||||
this._heap = heap;
|
||||
}
|
||||
// tslint:disable:no-floating-promises
|
||||
// HeapAnalytics Wrappers
|
||||
public identify(id: string, idType: string): void {
|
||||
this._heapLoadedGuardAsync(() => this._heap.identify(id, idType));
|
||||
}
|
||||
public track(eventName: string, eventProperties?: ObjectMap<string | number>): void {
|
||||
this._heapLoadedGuardAsync(() => this._heap.track(eventName, eventProperties));
|
||||
}
|
||||
public resetIdentity(): void {
|
||||
this._heapLoadedGuardAsync(() => this._heap.resetIdentity());
|
||||
}
|
||||
public addUserProperties(properties: ObjectMap<string | number>): void {
|
||||
this._heapLoadedGuardAsync(() => this._heap.addUserProperties(properties));
|
||||
}
|
||||
public addEventProperties(properties: ObjectMap<string | number>): void {
|
||||
this._heapLoadedGuardAsync(() => this._heap.addEventProperties(properties));
|
||||
}
|
||||
public removeEventProperty(property: string): void {
|
||||
this._heapLoadedGuardAsync(() => this._heap.removeEventProperty(property));
|
||||
}
|
||||
public clearEventProperties(): void {
|
||||
this._heapLoadedGuardAsync(() => this._heap.clearEventProperties());
|
||||
}
|
||||
// tslint:enable:no-floating-promises
|
||||
// Custom methods
|
||||
public trackOrderEvent(eventName: string, order: Order): void {
|
||||
const orderLoggingData = {
|
||||
takerTokenAmount: order.signedOrder.takerTokenAmount,
|
||||
makeTokenAmount: order.signedOrder.makerTokenAmount,
|
||||
takerToken: order.metadata.takerToken.symbol,
|
||||
makerToken: order.metadata.makerToken.symbol,
|
||||
};
|
||||
this.track(eventName, orderLoggingData);
|
||||
}
|
||||
/**
|
||||
* Heap is not available as a UMD module, and additionally has the strange property of replacing itself with
|
||||
* a different object once it's loaded.
|
||||
* Instead of having an await call before every analytics use, we opt to have the awaiting logic in here by
|
||||
* guarding every API call with the guard below.
|
||||
*/
|
||||
private async _heapLoadedGuardAsync(callback: () => void): Promise<void> {
|
||||
if (this._heap.loaded) {
|
||||
callback();
|
||||
return undefined;
|
||||
}
|
||||
await utils.onPageLoadPromise;
|
||||
// HACK: Reset heap to loaded heap
|
||||
this._heap = (window as any).heap;
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
export const analytics = Analytics.init();
|
||||
|
||||
@@ -313,14 +313,13 @@ export const utils = {
|
||||
const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`;
|
||||
return baseUrl;
|
||||
},
|
||||
async onPageLoadAsync(): Promise<void> {
|
||||
onPageLoadPromise: new Promise((resolve, _reject) => {
|
||||
if (document.readyState === 'complete') {
|
||||
return; // Already loaded
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
return new Promise<void>((resolve, _reject) => {
|
||||
window.onload = () => resolve();
|
||||
});
|
||||
},
|
||||
window.onload = resolve;
|
||||
}),
|
||||
getProviderType(provider: Provider): Providers | string {
|
||||
const constructorName = provider.constructor.name;
|
||||
let parsedProviderName = constructorName;
|
||||
|
||||
Reference in New Issue
Block a user