diff --git a/src/analytics.ts b/src/analytics.ts new file mode 100644 index 0000000..337f9d2 --- /dev/null +++ b/src/analytics.ts @@ -0,0 +1,4 @@ +/** Sends an event to Google Analytics. */ +export function analyticsEvent(action: string, data?: any) { + (window as any).gtag('event', action, data); +} diff --git a/src/app.tsx b/src/app.tsx index dc46706..fe7c49d 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,5 +1,6 @@ import * as queryString from 'query-string'; import * as React from 'react'; +import {analyticsEvent} from './analytics'; import {Chart} from './chart'; import {Details} from './details'; import {getSelection, loadFromUrl, loadGedcom} from './load_data'; @@ -8,7 +9,7 @@ import {Intro} from './intro'; import {Loader, Message, Responsive} from 'semantic-ui-react'; import {Redirect, Route, RouteComponentProps, Switch} from 'react-router-dom'; import {TopBar} from './top_bar'; -import {TopolaData} from './gedcom_util'; +import {TopolaData, getSoftware} from './gedcom_util'; /** Shows an error message. */ export function ErrorMessage(props: {message: string}) { @@ -92,6 +93,15 @@ export class App extends React.Component { const data = hash ? await loadGedcom(hash, gedcom, images) : await loadFromUrl(url!, handleCors); + analyticsEvent(hash ? 'upload_file_loaded' : 'url_file_loaded'); + if (images && images.size) { + analyticsEvent('images_uploaded'); + } + const software = getSoftware(data.gedcom.head); + if (software) { + analyticsEvent('gedcom_software', {event_label: software}); + } + // Set state with data. this.setState( Object.assign({}, this.state, { @@ -105,6 +115,7 @@ export class App extends React.Component { }), ); } catch (error) { + analyticsEvent(hash ? 'upload_file_error' : 'url_file_error'); // Set error state. this.setState( Object.assign({}, this.state, { @@ -138,6 +149,7 @@ export class App extends React.Component { * Updates the browser URL. */ private onSelection = (selection: IndiInfo) => { + analyticsEvent('selection_changed'); const location = this.props.location; const search = queryString.parse(location.search); search.indi = selection.id; @@ -187,10 +199,22 @@ export class App extends React.Component { this.state.selection ) } - onPrint={() => this.chartRef && this.chartRef.print()} - onDownloadPdf={() => this.chartRef && this.chartRef.downloadPdf()} - onDownloadPng={() => this.chartRef && this.chartRef.downloadPng()} - onDownloadSvg={() => this.chartRef && this.chartRef.downloadSvg()} + onPrint={() => { + analyticsEvent('print'); + this.chartRef && this.chartRef.print(); + }} + onDownloadPdf={() => { + analyticsEvent('download_pdf'); + this.chartRef && this.chartRef.downloadPdf(); + }} + onDownloadPng={() => { + analyticsEvent('download_png'); + this.chartRef && this.chartRef.downloadPng(); + }} + onDownloadSvg={() => { + analyticsEvent('download_svg'); + this.chartRef && this.chartRef.downloadSvg(); + }} /> )} /> diff --git a/src/gedcom_util.ts b/src/gedcom_util.ts index e3ccaa6..f2af7fb 100644 --- a/src/gedcom_util.ts +++ b/src/gedcom_util.ts @@ -163,3 +163,11 @@ export function convertGedcom( gedcom: prepareGedcom(entries), }; } + +export function getSoftware(head: GedcomEntry): string | null { + const sour = + head && head.tree && head.tree.find((entry) => entry.tag === 'SOUR'); + const name = + sour && sour.tree && sour.tree.find((entry) => entry.tag === 'NAME'); + return (name && name.data) || null; +} diff --git a/src/top_bar.tsx b/src/top_bar.tsx index 4c439a6..27d766a 100644 --- a/src/top_bar.tsx +++ b/src/top_bar.tsx @@ -1,6 +1,7 @@ import * as queryString from 'query-string'; import * as React from 'react'; import md5 from 'md5'; +import {analyticsEvent} from './analytics'; import {FormattedMessage} from 'react-intl'; import {Link} from 'react-router-dom'; import {RouteComponentProps} from 'react-router-dom'; @@ -59,6 +60,9 @@ export class TopBar extends React.Component< } const filesArray = Array.from(files); (event.target as HTMLInputElement).value = ''; // Reset the file input. + analyticsEvent('upload_files_selected', { + event_value: files.length, + }); const gedcomFile = files.length === 1 @@ -114,6 +118,7 @@ export class TopBar extends React.Component< }), ); if (this.state.url) { + analyticsEvent('url_selected'); this.props.history.push({ pathname: '/view', search: queryString.stringify({url: this.state.url}),