kopia lustrzana https://github.com/bugout-dev/moonstream
Merge branch 'main' into humbug-integration
commit
120b6a952c
|
@ -10,23 +10,20 @@ import (
|
|||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
humbug "github.com/bugout-dev/humbug/go/pkg"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Generate humbug client to be able write data in Bugout journal.
|
||||
func humbugClientFromEnv() (*humbug.HumbugReporter, error) {
|
||||
clientID := os.Getenv("ETHTXPOOL_HUMBUG_CLIENT_ID")
|
||||
humbugToken := os.Getenv("ETHTXPOOL_HUMBUG_TOKEN")
|
||||
sessionID := uuid.New().String()
|
||||
|
||||
// Generate humbug client
|
||||
func humbugClient(sessionID string, clientID string, humbugToken string) (*humbug.HumbugReporter, error) {
|
||||
consent := humbug.CreateHumbugConsent(humbug.True)
|
||||
reporter, err := humbug.CreateHumbugReporter(consent, clientID, sessionID, humbugToken)
|
||||
return reporter, err
|
||||
|
@ -124,11 +121,6 @@ func PollTxpoolContent(gethClient *rpc.Client, interval int, reporter *humbug.Hu
|
|||
continue
|
||||
}
|
||||
|
||||
// TODO(kompotkot, zomglings): Humbug API (on Spire) support bulk publication of reports. We should modify
|
||||
// Humbug go client to use the bulk publish endpoint. Currently, if we have to publish all transactions
|
||||
// pending in txpool, we *will* get rate limited. We may want to consider adding a publisher to the
|
||||
// Humbug go client that can listen on a channel and will handle rate limiting, bulk publication etc. itself
|
||||
// (without user having to worry about it).
|
||||
ReportTitle := "Ethereum: Pending transaction: " + transactionHash.String()
|
||||
ReportTags := []string{
|
||||
"hash:" + transactionHash.String(),
|
||||
|
@ -138,6 +130,7 @@ func PollTxpoolContent(gethClient *rpc.Client, interval int, reporter *humbug.Hu
|
|||
fmt.Sprintf("max_priority_fee_per_gas:%d", pendingTx.Transaction.MaxPriorityFeePerGas.ToInt()),
|
||||
fmt.Sprintf("max_fee_per_gas:%d", pendingTx.Transaction.MaxFeePerGas.ToInt()),
|
||||
fmt.Sprintf("gas:%d", pendingTx.Transaction.Gas),
|
||||
fmt.Sprintf("value:%d", new(big.Float).Quo(new(big.Float).SetInt(transaction.Value.ToInt()), big.NewFloat(params.Ether))),
|
||||
"crawl_type:ethereum_txpool",
|
||||
}
|
||||
report := humbug.Report{
|
||||
|
@ -188,6 +181,23 @@ func main() {
|
|||
flag.IntVar(&intervalSeconds, "interval", 1, "Number of seconds to wait between RPC calls to query the transaction pool (default: 1)")
|
||||
flag.Parse()
|
||||
|
||||
sessionID := uuid.New().String()
|
||||
|
||||
// Humbug crash client to collect errors
|
||||
crashReporter, err := humbugClient(sessionID, "moonstream-crawlers", os.Getenv("HUMBUG_REPORTER_CRAWLERS_TOKEN"))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Invalid Humbug Crash configuration: %s", err.Error()))
|
||||
}
|
||||
crashReporter.Publish(humbug.SystemReport())
|
||||
|
||||
defer func() {
|
||||
message := recover()
|
||||
if message != nil {
|
||||
fmt.Printf("Error: %s\n", message)
|
||||
crashReporter.Publish(humbug.PanicReport(message))
|
||||
}
|
||||
}()
|
||||
|
||||
// Set connection with Ethereum blockchain via geth
|
||||
gethClient, err := rpc.Dial(gethConnectionString)
|
||||
if err != nil {
|
||||
|
@ -195,7 +205,8 @@ func main() {
|
|||
}
|
||||
defer gethClient.Close()
|
||||
|
||||
reporter, err := humbugClientFromEnv()
|
||||
// Humbug client to be able write data in Bugout journal
|
||||
reporter, err := humbugClient(sessionID, os.Getenv("ETHTXPOOL_HUMBUG_CLIENT_ID"), os.Getenv("ETHTXPOOL_HUMBUG_TOKEN"))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Invalid Humbug configuration: %s", err.Error()))
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export ETHTXPOOL_HUMBUG_CLIENT_ID="<client id for the crawling machine>"
|
||||
export ETHTXPOOL_HUMBUG_TOKEN="<Generate an integration and a Humbug token from https://bugout.dev/account/teams>"
|
||||
export HUMBUG_REPORTER_CRAWLERS_TOKEN="<Bugout Humbug token for crash reports>"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
from .reporter import reporter
|
||||
from .version import MOONCRAWL_VERSION
|
||||
|
||||
# Reporting
|
||||
reporter.tags.append(f"version:{MOONCRAWL_VERSION}")
|
||||
reporter.system_report(publish=True)
|
||||
reporter.setup_excepthook(publish=True)
|
|
@ -48,7 +48,7 @@ def yield_blocks_numbers_lists(
|
|||
print(
|
||||
"Wrong format provided, expected {bottom_block}-{top_block}, as ex. 105-340"
|
||||
)
|
||||
return
|
||||
raise Exception
|
||||
|
||||
starting_block = max(input_start_block, input_end_block)
|
||||
ending_block = min(input_start_block, input_end_block)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import uuid
|
||||
|
||||
from humbug.consent import HumbugConsent
|
||||
from humbug.report import HumbugReporter
|
||||
|
||||
from .settings import HUMBUG_REPORTER_CRAWLERS_TOKEN
|
||||
|
||||
session_id = str(uuid.uuid4())
|
||||
client_id = "moonstream-crawlers"
|
||||
|
||||
reporter = HumbugReporter(
|
||||
name="moonstream-crawlers",
|
||||
consent=HumbugConsent(True),
|
||||
client_id=client_id,
|
||||
session_id=session_id,
|
||||
bugout_token=HUMBUG_REPORTER_CRAWLERS_TOKEN,
|
||||
tags=[],
|
||||
)
|
|
@ -1,5 +1,9 @@
|
|||
import os
|
||||
|
||||
# Bugout
|
||||
HUMBUG_REPORTER_CRAWLERS_TOKEN = os.environ.get("HUMBUG_REPORTER_CRAWLERS_TOKEN")
|
||||
|
||||
# Geth
|
||||
MOONSTREAM_IPC_PATH = os.environ.get("MOONSTREAM_IPC_PATH", None)
|
||||
|
||||
MOONSTREAM_CRAWL_WORKERS = 4
|
||||
|
@ -12,5 +16,5 @@ except:
|
|||
f"Could not parse MOONSTREAM_CRAWL_WORKERS as int: {MOONSTREAM_CRAWL_WORKERS_RAW}"
|
||||
)
|
||||
|
||||
|
||||
# Etherscan
|
||||
MOONSTREAM_ETHERSCAN_TOKEN = os.environ.get("MOONSTREAM_ETHERSCAN_TOKEN")
|
||||
|
|
|
@ -6,3 +6,4 @@ export MOONSTREAM_ETHERSCAN_TOKEN="<Token for etherscan>"
|
|||
export AWS_S3_SMARTCONTRACT_BUCKET="<AWS S3 bucket for smart contracts>"
|
||||
export MOONSTREAM_HUMBUG_TOKEN="<Token for crawlers store data via Humbug>"
|
||||
export COINMARKETCAP_API_KEY="<API key to parse conmarketcap>"
|
||||
export HUMBUG_REPORTER_CRAWLERS_TOKEN="<Bugout Humbug token for crash reports>"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from setuptools import find_packages, setup
|
||||
|
||||
from mooncrawl.version import MOONCRAWL_VERSION
|
||||
|
||||
long_description = ""
|
||||
with open("README.md") as ifp:
|
||||
|
@ -8,7 +7,7 @@ with open("README.md") as ifp:
|
|||
|
||||
setup(
|
||||
name="mooncrawl",
|
||||
version=MOONCRAWL_VERSION,
|
||||
version="0.0.3",
|
||||
author="Bugout.dev",
|
||||
author_email="engineers@bugout.dev",
|
||||
license="Apache License 2.0",
|
||||
|
@ -34,6 +33,7 @@ setup(
|
|||
zip_safe=False,
|
||||
install_requires=[
|
||||
"moonstreamdb @ git+https://git@github.com/bugout-dev/moonstream.git@39d2b8e36a49958a9ae085ec2cc1be3fc732b9d0#egg=moonstreamdb&subdirectory=db",
|
||||
"humbug",
|
||||
"python-dateutil",
|
||||
"requests",
|
||||
"tqdm",
|
||||
|
|
|
@ -24,12 +24,15 @@ import {
|
|||
} from "@chakra-ui/react";
|
||||
import dynamic from "next/dynamic";
|
||||
import useUser from "../src/core/hooks/useUser";
|
||||
import useAnalytics from "../src/core/hooks/useAnalytics";
|
||||
import useModals from "../src/core/hooks/useModals";
|
||||
import useRouter from "../src/core/hooks/useRouter";
|
||||
import { MIXPANEL_PROPS } from "../src/core/providers/AnalyticsProvider/constants";
|
||||
import {
|
||||
MIXPANEL_PROPS,
|
||||
MIXPANEL_EVENTS,
|
||||
} from "../src/core/providers/AnalyticsProvider/constants";
|
||||
import UIContext from "../src/core/providers/UIProvider/context";
|
||||
import { AWS_ASSETS_PATH } from "../src/core/constants";
|
||||
import mixpanel from "mixpanel-browser";
|
||||
const SplitWithImage = dynamic(
|
||||
() => import("../src/components/SplitWithImage"),
|
||||
{
|
||||
|
@ -105,7 +108,6 @@ const Homepage = () => {
|
|||
|
||||
const router = useRouter();
|
||||
const { isInit } = useUser();
|
||||
const { MIXPANEL_EVENTS, track } = useAnalytics();
|
||||
const { toggleModal } = useModals();
|
||||
const [
|
||||
isLargerThan720px,
|
||||
|
@ -379,27 +381,30 @@ const Homepage = () => {
|
|||
label: "Crypto trader",
|
||||
link: "/#cryptoTrader",
|
||||
onClick: () => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `scroll to CryptoTrader`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `scroll to CryptoTrader`,
|
||||
});
|
||||
},
|
||||
}}
|
||||
button2={{
|
||||
label: "Algorithmic Fund",
|
||||
link: "/#algoFund",
|
||||
onClick: () => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `scroll to AlgoFund`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `scroll to AlgoFund`,
|
||||
});
|
||||
},
|
||||
}}
|
||||
button3={{
|
||||
label: "Developer",
|
||||
link: "/#smartDeveloper",
|
||||
onClick: () => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `scroll to Developer`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `scroll to Developer`,
|
||||
});
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
@ -417,9 +422,10 @@ const Homepage = () => {
|
|||
cta={{
|
||||
label: "I want early access!",
|
||||
onClick: () => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `Early access CTA: Crypto trader`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: Crypto trader`,
|
||||
});
|
||||
toggleModal("hubspot-trader");
|
||||
},
|
||||
}}
|
||||
|
@ -464,9 +470,10 @@ const Homepage = () => {
|
|||
cta={{
|
||||
label: "I want early access!",
|
||||
onClick: () => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `Early access CTA: Algo fund`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: Algo fund`,
|
||||
});
|
||||
toggleModal("hubspot-fund");
|
||||
},
|
||||
}}
|
||||
|
@ -509,9 +516,10 @@ const Homepage = () => {
|
|||
cta={{
|
||||
label: "I want early access!",
|
||||
onClick: () => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `Early access CTA: developer`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: developer`,
|
||||
});
|
||||
toggleModal("hubspot-developer");
|
||||
},
|
||||
}}
|
||||
|
@ -520,9 +528,10 @@ const Homepage = () => {
|
|||
network: "github",
|
||||
label: "See our github",
|
||||
onClick: () => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `Github link in landing page`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Github link in landing page`,
|
||||
});
|
||||
},
|
||||
}}
|
||||
elementName={"element3"}
|
||||
|
@ -568,9 +577,10 @@ const Homepage = () => {
|
|||
colorScheme="suggested"
|
||||
id="test"
|
||||
onClick={() => {
|
||||
track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_CLICKED}`]: `Join our discord`,
|
||||
});
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
|
||||
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Join our discord`,
|
||||
});
|
||||
toggleModal("hubspot");
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -13,13 +13,13 @@ const AppContext = (props) => {
|
|||
return (
|
||||
<UserProvider>
|
||||
<ModalProvider>
|
||||
<AnalyticsProvider>
|
||||
<StripeProvider>
|
||||
<ChakraProvider theme={theme}>
|
||||
<UIProvider>{props.children}</UIProvider>
|
||||
</ChakraProvider>
|
||||
</StripeProvider>
|
||||
</AnalyticsProvider>
|
||||
<StripeProvider>
|
||||
<ChakraProvider theme={theme}>
|
||||
<UIProvider>
|
||||
<AnalyticsProvider>{props.children}</AnalyticsProvider>
|
||||
</UIProvider>
|
||||
</ChakraProvider>
|
||||
</StripeProvider>
|
||||
</ModalProvider>
|
||||
</UserProvider>
|
||||
);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Flex, Box } from "@chakra-ui/react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useRouter, useAnalytics } from "../core/hooks";
|
||||
import { useRouter } from "../core/hooks";
|
||||
import mixpanel from "mixpanel-browser";
|
||||
const Scrollable = (props) => {
|
||||
const scrollerRef = useRef();
|
||||
const router = useRouter();
|
||||
const [path, setPath] = useState();
|
||||
|
||||
const [scrollDepth, setScrollDepth] = useState(0);
|
||||
const { mixpanel, isLoaded } = useAnalytics();
|
||||
|
||||
const getScrollPrecent = ({ currentTarget }) => {
|
||||
const scroll_level =
|
||||
|
@ -20,7 +20,7 @@ const Scrollable = (props) => {
|
|||
const currentScroll = Math.ceil(getScrollPrecent(e) / 10);
|
||||
if (currentScroll > scrollDepth) {
|
||||
setScrollDepth(currentScroll);
|
||||
isLoaded &&
|
||||
mixpanel.get_distinct_id() &&
|
||||
mixpanel.people.increment({
|
||||
[`Scroll depth at: ${router.nextRouter.pathname}`]: currentScroll,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export { queryCacheProps as hookCommon } from "./hookCommon";
|
||||
export { default as useAnalytics } from "./useAnalytics";
|
||||
export { default as useAuthResultHandler } from "./useAuthResultHandler";
|
||||
export { default as useChangePassword } from "./useChangePassword";
|
||||
export { default as useClientID } from "./useClientID";
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import AnalyticsContext from "../providers/AnalyticsProvider/context";
|
||||
import { useContext } from "react";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
const useAnalytics = () => {
|
||||
const { mixpanel, isLoaded, MIXPANEL_EVENTS, MIXPANEL_PROPS } =
|
||||
useContext(AnalyticsContext);
|
||||
const [trackProps, setTrackProps] = useState({
|
||||
event: null,
|
||||
props: null,
|
||||
queued: false,
|
||||
});
|
||||
|
||||
const track = useCallback((e, props) => {
|
||||
setTrackProps({ event: e, props: props, queued: true });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoaded && trackProps.queued === true) {
|
||||
mixpanel.track(trackProps.event, trackProps.props);
|
||||
setTrackProps({ event: null, props: null, queued: false });
|
||||
}
|
||||
}, [isLoaded, mixpanel, trackProps]);
|
||||
|
||||
const withTracking = (fn, event, props) => {
|
||||
track(event, props);
|
||||
return fn;
|
||||
};
|
||||
|
||||
return {
|
||||
mixpanel,
|
||||
isLoaded,
|
||||
track,
|
||||
MIXPANEL_PROPS,
|
||||
MIXPANEL_EVENTS,
|
||||
withTracking,
|
||||
};
|
||||
};
|
||||
export default useAnalytics;
|
|
@ -1,7 +1,6 @@
|
|||
import { useMutation } from "react-query";
|
||||
import { useToast, useUser, useInviteAccept } from ".";
|
||||
import { AuthService } from "../services";
|
||||
import { useAnalytics } from ".";
|
||||
|
||||
const LOGIN_TYPES = {
|
||||
MANUAL: true,
|
||||
|
@ -10,7 +9,6 @@ const LOGIN_TYPES = {
|
|||
const useLogin = (loginType) => {
|
||||
const { getUser } = useUser();
|
||||
const toast = useToast();
|
||||
const analytics = useAnalytics();
|
||||
const { inviteAccept } = useInviteAccept();
|
||||
const {
|
||||
mutate: login,
|
||||
|
@ -34,20 +32,6 @@ const useLogin = (loginType) => {
|
|||
inviteAccept(invite_code);
|
||||
}
|
||||
getUser();
|
||||
if (analytics.isLoaded) {
|
||||
analytics.mixpanel.people.set_once({
|
||||
[`${analytics.MIXPANEL_EVENTS.FIRST_LOGIN_DATE}`]:
|
||||
new Date().toISOString(),
|
||||
});
|
||||
analytics.mixpanel.people.set({
|
||||
[`${analytics.MIXPANEL_EVENTS.LAST_LOGIN_DATE}`]:
|
||||
new Date().toISOString(),
|
||||
});
|
||||
analytics.mixpanel.track(
|
||||
`${analytics.MIXPANEL_EVENTS.USER_LOGS_IN}`,
|
||||
{}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
import { useCallback, useContext } from "react";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { useUser, useRouter, useAnalytics } from ".";
|
||||
import { useUser, useRouter } from ".";
|
||||
import UIContext from "../providers/UIProvider/context";
|
||||
import { AuthService } from "../services";
|
||||
|
||||
const useLogout = () => {
|
||||
const { setLoggingOut } = useContext(UIContext);
|
||||
const router = useRouter();
|
||||
const analytics = useAnalytics();
|
||||
const { mutate: revoke } = useMutation(AuthService.revoke, {
|
||||
onSuccess: () => {
|
||||
if (analytics.isLoaded) {
|
||||
analytics.mixpanel.track(
|
||||
`${analytics.MIXPANEL_EVENTS.USER_LOGS_OUT}`,
|
||||
{}
|
||||
);
|
||||
}
|
||||
localStorage.removeItem("MOONSTREAM_ACCESS_TOKEN");
|
||||
cache.clear();
|
||||
setUser(null);
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import { useContext } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { AuthService } from "../services";
|
||||
import { useUser, useToast, useInviteAccept, useRouter, useAnalytics } from ".";
|
||||
import { useUser, useToast, useInviteAccept, useRouter } from ".";
|
||||
import UIContext from "../providers/UIProvider/context";
|
||||
import mixpanel from "mixpanel-browser";
|
||||
import { MIXPANEL_EVENTS } from "../providers/AnalyticsProvider/constants";
|
||||
|
||||
const useSignUp = (source) => {
|
||||
const ui = useContext(UIContext);
|
||||
|
||||
const router = useRouter();
|
||||
const { getUser } = useUser();
|
||||
const toast = useToast();
|
||||
const { inviteAccept } = useInviteAccept();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const {
|
||||
mutate: signUp,
|
||||
|
@ -26,11 +28,11 @@ const useSignUp = (source) => {
|
|||
inviteAccept(invite_code);
|
||||
}
|
||||
|
||||
if (analytics.isLoaded) {
|
||||
analytics.mixpanel.track(
|
||||
`${analytics.MIXPANEL_EVENTS.CONVERT_TO_USER}`,
|
||||
{ full_url: router.nextRouter.asPath, code: source }
|
||||
);
|
||||
if (mixpanel.get_distinct_id()) {
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.CONVERT_TO_USER}`, {
|
||||
full_url: router.nextRouter.asPath,
|
||||
code: source,
|
||||
});
|
||||
}
|
||||
getUser();
|
||||
ui.setisOnboardingComplete(false);
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
import { useToast as useChakraToast, Box } from "@chakra-ui/react";
|
||||
import React, { useCallback } from "react";
|
||||
import { useAnalytics } from ".";
|
||||
import mixpanel from "mixpanel-browser";
|
||||
import { MIXPANEL_EVENTS } from "../providers/AnalyticsProvider/constants";
|
||||
|
||||
const useToast = () => {
|
||||
const chakraToast = useChakraToast();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const toast = useCallback(
|
||||
(message, type) => {
|
||||
if (analytics.isLoaded && type === "error") {
|
||||
analytics.mixpanel.track(
|
||||
`${analytics.MIXPANEL_EVENTS.TOAST_ERROR_DISPLAYED}`,
|
||||
{
|
||||
status: message?.response?.status,
|
||||
detail: message?.response?.data.detail,
|
||||
}
|
||||
);
|
||||
if (mixpanel.get_distinct_id() && type === "error") {
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.TOAST_ERROR_DISPLAYED}`, {
|
||||
status: message?.response?.status,
|
||||
detail: message?.response?.data.detail,
|
||||
});
|
||||
}
|
||||
const background = type === "error" ? "unsafe.500" : "suggested.500";
|
||||
const userMessage =
|
||||
|
@ -43,7 +40,7 @@ const useToast = () => {
|
|||
),
|
||||
});
|
||||
},
|
||||
[chakraToast, analytics]
|
||||
[chakraToast]
|
||||
);
|
||||
|
||||
return toast;
|
||||
|
|
|
@ -8,6 +8,12 @@ export const MIXPANEL_PROPS = {
|
|||
USER_SPECIALITY: "user speciality",
|
||||
};
|
||||
|
||||
export const MIXPANEL_GROUPS = {
|
||||
DEVELOPERS: "developers",
|
||||
TRADERS: "traders",
|
||||
FUND: "funds",
|
||||
};
|
||||
|
||||
export const MIXPANEL_EVENTS = {
|
||||
FIRST_LOGIN_DATE: "First login date",
|
||||
LAST_LOGIN_DATE: "Last login date",
|
||||
|
@ -20,7 +26,14 @@ export const MIXPANEL_EVENTS = {
|
|||
PAGEVIEW: "Page view",
|
||||
PRICING_PLAN_CLICKED: "Pricing Plan clicked",
|
||||
BUTTON_CLICKED: "Button clicked",
|
||||
LEFT_PAGE: "Left page",
|
||||
BEACON: "beacon",
|
||||
ONBOARDING_COMPLETED: "Onbording complete",
|
||||
SESSIONS_COUNT: "Sessions Counter",
|
||||
ONBOARDING_STEP: "Onboarding step",
|
||||
ONBOARDING_STATE: "Onboarding state",
|
||||
TIMES_VISITED: "Page visit times",
|
||||
FORM_SUBMITTED: "form submitted",
|
||||
PAGEVIEW_DURATION: "Time spent on page",
|
||||
};
|
||||
|
||||
export default MIXPANEL_EVENTS;
|
||||
|
|
|
@ -1,66 +1,161 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import mixpanel from "mixpanel-browser";
|
||||
import AnalyticsContext from "./context";
|
||||
import { useClientID, useUser, useRouter } from "../../hooks";
|
||||
import { MIXPANEL_EVENTS, MIXPANEL_PROPS } from "./constants";
|
||||
import UIContext from "../UIProvider/context";
|
||||
|
||||
const AnalyticsProvider = ({ children }) => {
|
||||
const clientID = useClientID();
|
||||
const analytics = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
|
||||
const { user } = useUser();
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const { user, isInit } = useUser();
|
||||
const [isMixpanelReady, setIsLoaded] = useState(false);
|
||||
const router = useRouter();
|
||||
const ui = useContext(UIContext);
|
||||
|
||||
// ********** OBOARDING STATE **************
|
||||
useEffect(() => {
|
||||
if (ui.onboardingState && isMixpanelReady) {
|
||||
mixpanel.people.set(MIXPANEL_EVENTS.ONBOARDING_STATE, {
|
||||
state: { ...ui.onboardingState },
|
||||
});
|
||||
}
|
||||
}, [ui.onboardingState, isMixpanelReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ui.isOnboardingComplete && isMixpanelReady && user) {
|
||||
mixpanel.people.set(MIXPANEL_EVENTS.ONBOARDING_COMPLETED, true);
|
||||
}
|
||||
}, [ui.isOnboardingComplete, isMixpanelReady, user]);
|
||||
|
||||
// ********** ONBOARDING STEP and TIMING **************
|
||||
const [previousOnboardingStep, setPreviousOnboardingStep] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMixpanelReady && router.nextRouter.pathname === "/welcome") {
|
||||
if (!previousOnboardingStep) {
|
||||
mixpanel.time_event(MIXPANEL_EVENTS.ONBOARDING_STEP);
|
||||
setPreviousOnboardingStep(ui.onboardingStep);
|
||||
}
|
||||
if (
|
||||
previousOnboardingStep &&
|
||||
previousOnboardingStep !== ui.onboardingStep
|
||||
) {
|
||||
mixpanel.track(MIXPANEL_EVENTS.ONBOARDING_STEP, {
|
||||
step: previousOnboardingStep,
|
||||
isBeforeUnload: false,
|
||||
});
|
||||
setPreviousOnboardingStep(false);
|
||||
}
|
||||
} else if (previousOnboardingStep) {
|
||||
mixpanel.track(MIXPANEL_EVENTS.ONBOARDING_STEP, {
|
||||
step: previousOnboardingStep,
|
||||
isBeforeUnload: false,
|
||||
});
|
||||
setPreviousOnboardingStep(false);
|
||||
}
|
||||
}, [
|
||||
previousOnboardingStep,
|
||||
ui.onboardingStep,
|
||||
isMixpanelReady,
|
||||
router.nextRouter.pathname,
|
||||
]);
|
||||
|
||||
// ********** PING_PONG **************
|
||||
useEffect(() => {
|
||||
let durationSeconds = 0;
|
||||
|
||||
const intervalId =
|
||||
isLoaded &&
|
||||
isMixpanelReady &&
|
||||
setInterval(() => {
|
||||
durationSeconds = durationSeconds + 1;
|
||||
durationSeconds = durationSeconds + 30;
|
||||
mixpanel.track(
|
||||
MIXPANEL_EVENTS.LEFT_PAGE,
|
||||
MIXPANEL_EVENTS.BEACON,
|
||||
{
|
||||
duration_seconds: durationSeconds,
|
||||
url: router.nextRouter.pathname,
|
||||
query: router.query,
|
||||
pathParams: router.params,
|
||||
},
|
||||
{ transport: "sendBeacon" }
|
||||
);
|
||||
}, 30000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
// eslint-disable-next-line
|
||||
}, [isLoaded]);
|
||||
}, [isMixpanelReady, router.nextRouter.pathname]);
|
||||
|
||||
// ********** TIME SPENT ON PATH**************
|
||||
|
||||
const [previousPathname, setPreviousPathname] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
isLoaded &&
|
||||
if (isMixpanelReady) {
|
||||
if (!previousPathname) {
|
||||
mixpanel.time_event(MIXPANEL_EVENTS.PAGEVIEW_DURATION);
|
||||
setPreviousPathname(router.nextRouter.pathname);
|
||||
}
|
||||
if (previousPathname && previousPathname !== router.nextRouter.pathname) {
|
||||
mixpanel.track(MIXPANEL_EVENTS.PAGEVIEW_DURATION, {
|
||||
url: previousPathname,
|
||||
isBeforeUnload: false,
|
||||
});
|
||||
setPreviousPathname(false);
|
||||
}
|
||||
}
|
||||
}, [router.nextRouter.pathname, previousPathname, isMixpanelReady]);
|
||||
|
||||
// ********** PAGES VIEW **************
|
||||
useEffect(() => {
|
||||
if (isMixpanelReady && ui.sessionId && router.nextRouter.pathname) {
|
||||
mixpanel.track(MIXPANEL_EVENTS.PAGEVIEW, {
|
||||
url: router.nextRouter.pathname,
|
||||
query: router.query,
|
||||
pathParams: router.params,
|
||||
sessionID: ui.sessionId,
|
||||
});
|
||||
}, [router.nextRouter.pathname, router.query, router.params, isLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
mixpanel.init(analytics, {
|
||||
api_host: "https://api.mixpanel.com",
|
||||
loaded: () => {
|
||||
setIsLoaded(true);
|
||||
mixpanel.identify(clientID);
|
||||
},
|
||||
mixpanel.people.increment([
|
||||
`${MIXPANEL_EVENTS.TIMES_VISITED} ${router.nextRouter.pathname}`,
|
||||
]);
|
||||
}
|
||||
const urlForUnmount = router.nextRouter.pathname;
|
||||
const closeListener = () => {
|
||||
mixpanel.track(MIXPANEL_EVENTS.PAGEVIEW_DURATION, {
|
||||
url: urlForUnmount,
|
||||
isBeforeUnload: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("loading mixpanel failed:", error);
|
||||
};
|
||||
window.addEventListener("beforeunload", closeListener);
|
||||
//cleanup function fires on useEffect unmount
|
||||
//https://reactjs.org/docs/hooks-effect.html
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", closeListener);
|
||||
};
|
||||
}, [router.nextRouter.pathname, isMixpanelReady, ui.sessionId]);
|
||||
|
||||
// ********** SESSION STATE **************
|
||||
useEffect(() => {
|
||||
if (clientID) {
|
||||
try {
|
||||
mixpanel.init(analytics, {
|
||||
api_host: "https://api.mixpanel.com",
|
||||
loaded: () => {
|
||||
setIsLoaded(true);
|
||||
mixpanel.identify(clientID);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("loading mixpanel failed:", error);
|
||||
}
|
||||
}
|
||||
}, [analytics, clientID]);
|
||||
|
||||
useEffect(() => {
|
||||
isMixpanelReady && mixpanel.register("sessionId", ui.sessionId);
|
||||
}, [ui.sessionId, isMixpanelReady]);
|
||||
|
||||
// ********** USER STATE **************
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
try {
|
||||
if (isLoaded) {
|
||||
if (isMixpanelReady) {
|
||||
mixpanel.people.set({
|
||||
[`${MIXPANEL_EVENTS.LAST_VISITED}`]: new Date().toISOString(),
|
||||
});
|
||||
|
@ -74,11 +169,36 @@ const AnalyticsProvider = ({ children }) => {
|
|||
console.error("could not set up people in mixpanel:", err);
|
||||
}
|
||||
}
|
||||
}, [user, isLoaded, clientID]);
|
||||
}, [user, isMixpanelReady, clientID]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMixpanelReady && user) {
|
||||
mixpanel.people.set_once({
|
||||
[`${MIXPANEL_EVENTS.FIRST_LOGIN_DATE}`]: new Date().toISOString(),
|
||||
});
|
||||
mixpanel.people.set({
|
||||
[`${MIXPANEL_EVENTS.LAST_LOGIN_DATE}`]: new Date().toISOString(),
|
||||
});
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.USER_LOGS_IN}`, {});
|
||||
}
|
||||
}, [user, isMixpanelReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMixpanelReady && ui.isLoggingOut) {
|
||||
mixpanel.track(`${MIXPANEL_EVENTS.USER_LOGS_OUT}`, {});
|
||||
}
|
||||
}, [ui.isLoggingOut, isMixpanelReady]);
|
||||
|
||||
// ********** USER BOUNCE TIME **************
|
||||
useEffect(() => {
|
||||
if (!user && isInit && isMixpanelReady) {
|
||||
mixpanel.time_event(MIXPANEL_EVENTS.CONVERT_TO_USER);
|
||||
}
|
||||
}, [user, isInit, isMixpanelReady]);
|
||||
|
||||
return (
|
||||
<AnalyticsContext.Provider
|
||||
value={{ mixpanel, isLoaded, MIXPANEL_EVENTS, MIXPANEL_PROPS }}
|
||||
value={{ mixpanel, isMixpanelReady, MIXPANEL_EVENTS, MIXPANEL_PROPS }}
|
||||
>
|
||||
{children}
|
||||
</AnalyticsContext.Provider>
|
||||
|
|
|
@ -181,10 +181,19 @@ const UIProvider = ({ children }) => {
|
|||
);
|
||||
|
||||
const [onboardingStep, setOnboardingStep] = useState(() => {
|
||||
const step = onboardingSteps.findIndex(
|
||||
(event) => onboardingState[event.step] === 0
|
||||
//First find onboarding step that was viewed once (resume where left)
|
||||
// If none - find step that was never viewed
|
||||
// if none - set onboarding to zero
|
||||
let step = onboardingSteps.findIndex(
|
||||
(event) => onboardingState[event.step] === 1
|
||||
);
|
||||
if (step === -1 && isOnboardingComplete) return onboardingSteps.length - 1;
|
||||
step =
|
||||
step === -1
|
||||
? onboardingSteps.findIndex(
|
||||
(event) => onboardingState[event.step] === 0
|
||||
)
|
||||
: step;
|
||||
if (step === -1 && isOnboardingComplete) return 0;
|
||||
else if (step === -1 && !isOnboardingComplete) return 0;
|
||||
else return step;
|
||||
});
|
||||
|
@ -263,6 +272,8 @@ const UIProvider = ({ children }) => {
|
|||
setisOnboardingComplete,
|
||||
onboardingSteps,
|
||||
setOnboardingState,
|
||||
onboardingState,
|
||||
isLoggingOut,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
Ładowanie…
Reference in New Issue