Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ log grapher views to google analytics #4076

Merged
merged 1 commit into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion explorer/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,6 @@ export class Explorer
if (mapTargetTime) {
grapher.map.time = mapTargetTime
}
grapher.slug = this.explorerProgram.slug
if (!grapher.id) grapher.id = 0
}

Expand Down Expand Up @@ -548,6 +547,7 @@ export class Explorer
grapher.reset()
this.updateGrapherFromExplorerCommon()
grapher.updateFromObject(config)
grapher.slug = undefined
grapher.downloadData()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ export class ActionButtons extends React.Component<{
}

@computed private get hasShareButton(): boolean {
return !this.manager.hideShareButton
return (
!this.manager.hideShareButton && ShareMenu.shouldShow(this.manager)
)
}

@computed private get hasFullScreenButton(): boolean {
Expand Down
5 changes: 0 additions & 5 deletions packages/@ourworldindata/grapher/src/controls/ShareMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,3 @@ $zindex-ControlsFooter: 2;
}
}
}

.ShareMenu.disabled a {
pointer-events: none;
opacity: 0.3;
}
129 changes: 68 additions & 61 deletions packages/@ourworldindata/grapher/src/controls/ShareMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface ShareMenuManager {

interface ShareMenuProps {
manager: ShareMenuManager
onDismiss: () => void
onDismiss?: () => void
right?: number
}

Expand Down Expand Up @@ -91,6 +91,11 @@ export class ShareMenu extends React.Component<ShareMenuProps, ShareMenuState> {
}
}

static shouldShow(manager: ShareMenuManager): boolean {
const test = new ShareMenu({ manager })
return test.showShareMenu
}

@computed get manager(): ShareMenuManager {
return this.props.manager
}
Expand All @@ -99,16 +104,16 @@ export class ShareMenu extends React.Component<ShareMenuProps, ShareMenuState> {
return this.manager.currentTitle ?? ""
}

@computed get isDisabled(): boolean {
return !this.manager.slug
@computed get showShareMenu(): boolean {
return !!this.canonicalUrl || !!this.manager.editUrl
}

@computed get canonicalUrl(): string | undefined {
return this.manager.canonicalUrl
}

@action.bound dismiss(): void {
this.props.onDismiss()
this.props.onDismiss?.()
}

@action.bound onClickSomewhere(): void {
Expand Down Expand Up @@ -153,35 +158,31 @@ export class ShareMenu extends React.Component<ShareMenuProps, ShareMenuState> {
}
}

@computed get twitterHref(): string {
let href =
"https://twitter.com/intent/tweet/?text=" +
encodeURIComponent(this.title)
if (this.canonicalUrl)
href += "&url=" + encodeURIComponent(this.canonicalUrl)
return href
@computed get twitterHref(): string | undefined {
if (!this.canonicalUrl) return undefined
const queryParams = new URLSearchParams({
text: this.title,
url: this.canonicalUrl,
})
return `https://twitter.com/intent/tweet/?${queryParams}`
}

@computed get facebookHref(): string {
let href =
"https://www.facebook.com/dialog/share?app_id=1149943818390250&display=page"
if (this.canonicalUrl)
href += "&href=" + encodeURIComponent(this.canonicalUrl)
return href
@computed get facebookHref(): string | undefined {
if (!this.canonicalUrl) return undefined
const queryParams = new URLSearchParams({
app_id: "1149943818390250",
display: "page",
href: this.canonicalUrl,
})
return `https://www.facebook.com/dialog/share?${queryParams}`
}

@computed get canUseShareApi(): boolean {
return canUseShareApi(this.manager)
}

render(): React.ReactElement {
const {
twitterHref,
facebookHref,
isDisabled,
canUseShareApi,
manager,
} = this
const { twitterHref, facebookHref, canUseShareApi, manager } = this
const { editUrl } = manager

const width = 200
Expand All @@ -193,46 +194,52 @@ export class ShareMenu extends React.Component<ShareMenuProps, ShareMenuState> {

return (
<div
className={"ShareMenu" + (isDisabled ? " disabled" : "")}
className="ShareMenu"
onClick={action(() => (this.dismissable = false))}
style={style}
>
<h2>Share</h2>
<a
target="_blank"
title="Tweet a link"
data-track-note="chart_share_twitter"
href={twitterHref}
rel="noopener"
>
<span className="icon">
<FontAwesomeIcon icon={faXTwitter} />
</span>{" "}
X/Twitter
</a>
<a
target="_blank"
title="Share on Facebook"
data-track-note="chart_share_facebook"
href={facebookHref}
rel="noopener"
>
<span className="icon">
<FontAwesomeIcon icon={faFacebook} />
</span>{" "}
Facebook
</a>
<a
className="embed"
title="Embed this visualization in another HTML document"
data-track-note="chart_share_embed"
onClick={this.onEmbed}
>
<span className="icon">
<FontAwesomeIcon icon={faCode} />
</span>{" "}
Embed
</a>
{twitterHref && (
<a
target="_blank"
title="Tweet a link"
data-track-note="chart_share_twitter"
href={twitterHref}
rel="noopener"
>
<span className="icon">
<FontAwesomeIcon icon={faXTwitter} />
</span>{" "}
X/Twitter
</a>
)}
{facebookHref && (
<a
target="_blank"
title="Share on Facebook"
data-track-note="chart_share_facebook"
href={facebookHref}
rel="noopener"
>
<span className="icon">
<FontAwesomeIcon icon={faFacebook} />
</span>{" "}
Facebook
</a>
)}
{this.canonicalUrl && (
<a
className="embed"
title="Embed this visualization in another HTML document"
data-track-note="chart_share_embed"
onClick={this.onEmbed}
>
<span className="icon">
<FontAwesomeIcon icon={faCode} />
</span>{" "}
Embed
</a>
)}
{canUseShareApi && (
<a
title="Share this visualization with an app on your device"
Expand All @@ -245,7 +252,7 @@ export class ShareMenu extends React.Component<ShareMenuProps, ShareMenuState> {
Share via&hellip;
</a>
)}
{this.state.canWriteToClipboard && (
{this.state.canWriteToClipboard && this.canonicalUrl && (
<a
title="Copy link to clipboard"
data-track-note="chart_share_copylink"
Expand Down
10 changes: 9 additions & 1 deletion packages/@ourworldindata/grapher/src/core/Grapher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,8 @@ export class Grapher
devtools: {
track: "Grapher",
properties: [
["slug", this.slug],
// might be missing for charts within explorers or mdims
["slug", this.slug ?? "missing-slug"],
["chartType", this.type],
["tab", this.tab],
],
Expand Down Expand Up @@ -2342,6 +2343,7 @@ export class Grapher
return this.base.current || undefined
}

private hasLoggedGAViewEvent = false
@observable private hasBeenVisible = false
@observable private uncaughtError?: Error

Expand Down Expand Up @@ -2829,6 +2831,12 @@ export class Grapher
entries.forEach((entry) => {
if (entry.isIntersecting) {
this.hasBeenVisible = true

if (this.slug && !this.hasLoggedGAViewEvent) {
this.analytics.logGrapherView(this.slug)
this.hasLoggedGAViewEvent = true
}

observer.disconnect()
}
})
Expand Down
10 changes: 10 additions & 0 deletions packages/@ourworldindata/grapher/src/core/GrapherAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum EventCategory {
CountryProfileSearch = "owid.country_profile_search",
Filter = "owid.filter",
GlobalEntitySelectorUsage = "owid.global_entity_selector_usage",
GrapherView = "owid.grapher_view",
GrapherClick = "owid.grapher_click",
GrapherError = "owid.grapher_error",
ExplorerCountrySelector = "owid.explorer_country_selector",
Expand Down Expand Up @@ -48,6 +49,7 @@ interface GAEvent {
eventContext?: string
eventTarget?: string
grapherPath?: string
grapherView?: string // specifies a view in a multi-dim data page
}

// taken from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/de66435d18fbdb2684947d16b5cd3a77f876324c/types/gtag.js/index.d.ts#L151-L156
Expand All @@ -71,6 +73,14 @@ export class GrapherAnalytics {
private version: string // Ideally the Git hash commit
private isDev: boolean

logGrapherView(slug: string, view?: Record<string, string>): void {
this.logToGA({
event: EventCategory.GrapherView,
grapherPath: `/grapher/${slug}`,
grapherView: view ? JSON.stringify(view) : undefined,
})
}

logGrapherViewError(error: Error): void {
this.logToGA({
event: EventCategory.GrapherError,
Expand Down
14 changes: 11 additions & 3 deletions site/multiDim/MultiDimDataPageContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import {
Grapher,
GrapherAnalytics,
GrapherProgrammaticInterface,
getVariableMetadataRoute,
} from "@ourworldindata/grapher"
Expand Down Expand Up @@ -194,6 +195,8 @@ const useVarDatapageData = (
}
}

const analytics = new GrapherAnalytics()

export const MultiDimDataPageContent = ({
// _datapageData,
configObj,
Expand All @@ -207,6 +210,8 @@ export const MultiDimDataPageContent = ({
}: MultiDimDataPageProps) => {
const grapherFigureRef = useRef<HTMLDivElement>(null)

const slug = window?.location.pathname.split("/").pop()

const config = useMemo(
() => MultiDimDataPageConfig.fromObject(configObj),
[configObj]
Expand Down Expand Up @@ -262,6 +267,10 @@ export const MultiDimDataPageContent = ({
setWindowQueryStr(queryStr ?? "")
}, [queryStr])

useEffect(() => {
if (slug) analytics.logGrapherView(slug, currentSettings)
}, [slug, currentSettings])

const grapherConfigComputed = useMemo(() => {
const baseConfig: GrapherProgrammaticInterface = {
bounds,
Expand All @@ -273,12 +282,11 @@ export const MultiDimDataPageContent = ({
...varGrapherConfig,
...baseConfig,
dataApiUrl: DATA_API_URL,
// TODO: The way manager and slug are set here are just workarounds to make the edit button in the
// share menu work. They should be removed before we publish MDims!
// TODO: The way manager is set here is just a workaround to make the edit button in the
// share menu work. This should be removed before we publish MDims!
manager: {
canonicalUrl,
},
slug: "DUMMY",
} as GrapherProgrammaticInterface
}, [varGrapherConfig, grapherConfigIsReady, bounds, canonicalUrl])

Expand Down