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:
@@ -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()
|
||||
|
@@ -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';
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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,
|
||||
});
|
||||
|
81
hyperglass/ui/hooks/useGoogleAnalytics.tsx
Normal file
81
hyperglass/ui/hooks/useGoogleAnalytics.tsx
Normal 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 };
|
||||
}
|
@@ -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;
|
||||
|
7
hyperglass/ui/package.json
vendored
7
hyperglass/ui/package.json
vendored
@@ -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",
|
||||
|
@@ -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 (
|
||||
<>
|
||||
|
@@ -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;
|
||||
|
13
hyperglass/ui/yarn.lock
vendored
13
hyperglass/ui/yarn.lock
vendored
@@ -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"
|
||||
|
Reference in New Issue
Block a user