1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

re-implement google analytics

This commit is contained in:
checktheroads
2021-01-10 01:14:57 -07:00
parent ccf9bb1fc0
commit 4137286ca4
11 changed files with 148 additions and 14 deletions

View File

@@ -115,6 +115,7 @@ class Params(HyperglassModel):
title="Netmiko Delay Factor",
description="Override the netmiko global delay factor.",
)
google_analytics: Optional[StrictStr]
# Sub Level Params
cache: Cache = Cache()

View File

@@ -2,6 +2,7 @@ export * from './useASNDetail';
export * from './useBooleanValue';
export * from './useDevice';
export * from './useDNSQuery';
export * from './useGoogleAnalytics';
export * from './useGreeting';
export * from './useLGQuery';
export * from './useLGState';

View File

@@ -1,5 +1,6 @@
import { State } from '@hookstate/core';
import type { State } from '@hookstate/core';
import type { QueryFunctionContext } from 'react-query';
import type * as ReactGA from 'react-ga';
import type {
TDevice,
Families,
@@ -89,9 +90,14 @@ export type TLGStateHandlers = {
stateExporter<O extends unknown>(o: O): O | null;
};
export type UseStrfArgs = { [k: string]: any } | string;
export type UseStrfArgs = { [k: string]: unknown } | string;
export type TTableToStringFormatter = (v: any) => string;
export type TTableToStringFormatter =
| ((v: string) => string)
| ((v: number) => string)
| ((v: number[]) => string)
| ((v: string[]) => string)
| ((v: boolean) => string);
export type TTableToStringFormatted = {
age: (v: number) => string;
@@ -100,3 +106,13 @@ export type TTableToStringFormatted = {
communities: (v: string[]) => string;
rpki_state: (v: number, n: TRPKIStates) => string;
};
export type GAEffect = (ga: typeof ReactGA) => void;
export interface GAReturn {
ga: typeof ReactGA;
initialize(trackingId: string | null, debug: boolean): void;
trackPage(path: string): void;
trackModal(path: string): void;
trackEvent(event: ReactGA.EventArgs): void;
}

View File

@@ -1,6 +1,7 @@
import { useQuery } from 'react-query';
import { useConfig } from '~/context';
import { fetchWithTimeout } from '~/util';
import { useGoogleAnalytics } from './useGoogleAnalytics';
import type { QueryObserverResult } from 'react-query';
import type { DnsOverHttps } from '~/types';
@@ -49,6 +50,12 @@ export function useDNSQuery(
family: 4 | 6,
): QueryObserverResult<DnsOverHttps.Response> {
const { cache, web } = useConfig();
const { trackEvent } = useGoogleAnalytics();
if (typeof target === 'string') {
trackEvent({ category: 'DNS', action: 'Query', label: target, dimension1: `IPv${family}` });
}
return useQuery([web.dns_provider.url, { target, family }], dnsQuery, {
cacheTime: cache.timeout * 1000,
});

View File

@@ -0,0 +1,81 @@
import { useCallback } from 'react';
import { createState, useState } from '@hookstate/core';
import * as ReactGA from 'react-ga';
import type { GAEffect, GAReturn } from './types';
const enabledState = createState<boolean>(false);
export function useGoogleAnalytics(): GAReturn {
const enabled = useState<boolean>(enabledState);
const useAnalytics = useCallback((effect: GAEffect): void => {
if (typeof window !== 'undefined' && enabled.value) {
if (typeof effect === 'function') {
effect(ReactGA);
}
}
}, []);
const trackEvent = useCallback((e: ReactGA.EventArgs) => {
useAnalytics(ga => {
if (process.env.NODE_ENV === 'production') {
ga.event(e);
} else {
console.log(
`%cEvent %c${JSON.stringify(e)}`,
'background: green; color: black; padding: 0.5rem; font-size: 0.75rem;',
'background: black; color: green; padding: 0.5rem; font-size: 0.75rem; font-weight: bold;',
);
}
});
}, []);
const trackPage = useCallback((path: string) => {
useAnalytics(ga => {
if (process.env.NODE_ENV === 'production') {
ga.pageview(path);
} else {
console.log(
`%cPage View %c${path}`,
'background: blue; color: white; padding: 0.5rem; font-size: 0.75rem;',
'background: white; color: blue; padding: 0.5rem; font-size: 0.75rem; font-weight: bold;',
);
}
});
}, []);
const trackModal = useCallback((path: string) => {
useAnalytics(ga => {
if (process.env.NODE_ENV === 'production') {
ga.modalview(path);
} else {
console.log(
`%cModal View %c${path}`,
'background: red; color: white; padding: 0.5rem; font-size: 0.75rem;',
'background: white; color: red; padding: 0.5rem; font-size: 0.75rem; font-weight: bold;',
);
}
});
}, []);
const initialize = useCallback((trackingId: string, debug: boolean) => {
if (typeof trackingId !== 'string') {
return;
}
enabled.set(true);
const initializeOpts = { titleCase: false } as ReactGA.InitializeOptions;
if (debug) {
initializeOpts.debug = true;
}
useAnalytics(ga => {
ga.initialize(trackingId, initializeOpts);
});
}, []);
return { trackEvent, trackModal, trackPage, initialize, ga: ReactGA };
}

View File

@@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { useQuery } from 'react-query';
import { useConfig } from '~/context';
import { useGoogleAnalytics } from './useGoogleAnalytics';
import { fetchWithTimeout } from '~/util';
import type { QueryObserverResult } from 'react-query';
@@ -14,6 +15,17 @@ export function useLGQuery(query: TFormQuery): QueryObserverResult<TQueryRespons
const { request_timeout, cache } = useConfig();
const controller = new AbortController();
const { trackEvent } = useGoogleAnalytics();
trackEvent({
category: 'Query',
action: 'submit',
dimension1: query.queryLocation,
dimension2: query.queryTarget,
dimension3: query.queryType,
dimension4: query.queryVrf,
});
async function runQuery(ctx: TUseLGQueryFn): Promise<TQueryResponse> {
const [url, data] = ctx.queryKey;
const { queryLocation, queryTarget, queryType, queryVrf } = data;

View File

@@ -6,11 +6,11 @@
"license": "BSD-3-Clause-Clear",
"private": true,
"scripts": {
"lint": "eslint .",
"lint": "eslint . --ext .ts --ext .tsx",
"dev": "node nextdev",
"start": "next start",
"typecheck": "tsc --noEmit",
"format": "prettier -c -w .",
"format": "prettier -c .",
"clean": "rimraf --no-glob ./.next ./out",
"check:es:export": "es-check es5 './out/**/*.js' -v",
"check:es:build": "es-check es5 './.next/static/**/*.js' -v",
@@ -22,7 +22,7 @@
"@emotion/react": "^11.1.4",
"@emotion/styled": "^11.0.0",
"@hookform/resolvers": "^1.2.0",
"@hookstate/core": "^3.0.1",
"@hookstate/core": "^3.0.3",
"@hookstate/persistence": "^3.0.0",
"@meronex/icons": "^4.0.0",
"color2k": "^1.1.1",
@@ -36,6 +36,7 @@
"react-dom": "^17.0.1",
"react-fast-compare": "^3.2.0",
"react-flow-renderer": "^8.2.3",
"react-ga": "^3.3.0",
"react-hook-form": "^6.13.1",
"react-markdown": "^5.0.3",
"react-query": "^3.5.6",

View File

@@ -1,5 +1,7 @@
import { useEffect } from 'react';
import Head from 'next/head';
import { HyperglassProvider } from '~/context';
import { useGoogleAnalytics } from '~/hooks';
import { IConfig } from '~/types';
import type { AppProps, AppInitialProps, AppContext } from 'next/app';
@@ -12,13 +14,20 @@ type TApp = { config: IConfig };
type GetInitialPropsReturn<IP> = AppProps & AppInitialProps & { appProps: IP };
type Temp<IP> = React.FC<GetInitialPropsReturn<IP>> & {
type NextApp<IP> = React.FC<GetInitialPropsReturn<IP>> & {
getInitialProps(c?: AppContext): Promise<{ appProps: IP }>;
};
const App: Temp<TApp> = (props: GetInitialPropsReturn<TApp>) => {
const { Component, pageProps, appProps } = props;
const App: NextApp<TApp> = (props: GetInitialPropsReturn<TApp>) => {
const { Component, pageProps, appProps, router } = props;
const { config } = appProps;
const { initialize, trackPage } = useGoogleAnalytics();
initialize(config.google_analytics, config.developer_mode);
useEffect(() => {
router.events.on('routeChangeComplete', trackPage);
}, []);
return (
<>

View File

@@ -169,7 +169,7 @@ export interface IConfig {
primary_asn: string;
request_timeout: number;
org_name: string;
google_analytics?: string;
google_analytics: string | null;
site_title: string;
site_keywords: string[];
site_description: string;

View File

@@ -926,10 +926,10 @@
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-1.2.0.tgz#3a429b4bd3ec9981764fcdcc2491202f9f72d847"
integrity sha512-YCKEj/3Kdo3uNt+zrWKV8txaiuATtvgHyz+KYmun3n5JDjxdI0HcVQgfcmJabmkBXBzKuIIrYfxaV8sRuAPZ8w==
"@hookstate/core@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-3.0.1.tgz#dc46ea71e3bf0ab5c2dc024029c9210ed12fbb84"
integrity sha512-buRie83l3FYPLCuaBE68puE3XS19r2O+jwC/kH2ikIW7ww8AavndR3MspzMMkNpq2zL8pZtIcpiWJBbn4Uq2Vw==
"@hookstate/core@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-3.0.3.tgz#9fdd0e29d3cd4f3ce70ec209071d9d31b3a33677"
integrity sha512-tlcBxWrOZCEw3ExHTlvLK93q+JPV6d1BayjvFkXIPDCNC0fSc/aXOWjE7gywTnUYrrsidYNiOBjAROHUIWHoUw==
"@hookstate/devtools@^3.0.0":
version "3.0.0"
@@ -6189,6 +6189,11 @@ react-focus-lock@2.4.1:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
react-ga@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.0.tgz#c91f407198adcb3b49e2bc5c12b3fe460039b3ca"
integrity sha512-o8RScHj6Lb8cwy3GMrVH6NJvL+y0zpJvKtc0+wmH7Bt23rszJmnqEQxRbyrqUzk9DTJIHoP42bfO5rswC9SWBQ==
react-hook-form@^6.13.1:
version "6.13.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.13.1.tgz#b9c0aa61f746db8169ed5e1050de21cacb1947d6"