Skip to content

Commit

Permalink
Only load Segment's script if gatherUserStats is true (streamlit#1723)
Browse files Browse the repository at this point in the history
* Dynamically loading Segment-io script

* Added Segment.ts

* Removed unused function

* fixed linting issues

* fixed linting issues with Segment.ts

* fixed frontend test cases

* Changes based on comments

* Corrected spellings and removed package-lock.json

* Update .gitignore

removed unnecessary space

* removed window check
  • Loading branch information
tanmaylaud authored Jul 22, 2020
1 parent b2c3bec commit 7cd06d5
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 10 deletions.
8 changes: 0 additions & 8 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@

<title>Streamlit</title>

<!-- Segment.io -->
<script>
// prettier-ignore
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t,e){var n=document.createElement("script");n.type="text/javascript";n.async=!0;n.src="https://cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(n,a);analytics._loadOptions=e};analytics.SNIPPET_VERSION="4.1.0";
analytics.load("iCkMy7ymtJ9qYzQRXkQpnAJEq7D4NyMU");
}}();
</script>

<!-- load via script to enable web workers -->
<script
src="%PUBLIC_URL%/vendor/viz/viz-1.8.0.min.js"
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/lib/MetricsManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ beforeEach(() => {

afterEach(() => {
SessionInfo["singleton"] = undefined
window.analytics = undefined
})

test("does not track while uninitialized", () => {
Expand All @@ -58,6 +59,23 @@ test("does not track when initialized with gatherUsageStats=false", () => {
expect(mm["identify"].mock.calls.length).toBe(0)
})

test("does not initialize Segment analytics when gatherUsageStats=false", () => {
const mm = getMetricsManagerForTest()
expect(window.analytics).toBeUndefined()
mm.initialize({ gatherUsageStats: false })
expect(window.analytics).toBeUndefined()
})

test("initializes Segment analytics when gatherUsageStats=true", () => {
const mm = getMetricsManagerForTest()
expect(window.analytics).toBeUndefined()
mm.initialize({ gatherUsageStats: true })
expect(window.analytics).toBeDefined()
expect(window.analytics.invoked).toBe(true)
expect(window.analytics.methods).toHaveLength(20)
expect(window.analytics.load).toBeDefined()
})

test("enqueues events before initialization", () => {
const mm = getMetricsManagerForTest()

Expand Down
9 changes: 7 additions & 2 deletions frontend/src/lib/MetricsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
import { IS_DEV_ENV, IS_SHARED_REPORT } from "./baseconsts"
import { SessionInfo } from "lib/SessionInfo"
import { logAlways } from "./log"
import { initializeSegment } from "./Segment"

/**
* The analytics is the Segment.io object. It comes from index.html.
*/
* The analytics is the Segment.io object. It is initialized in Segment.ts
* It is loaded with global scope (window.analytics) to integrate with the segment.io api
* @global
* */
declare const analytics: any

/**
Expand Down Expand Up @@ -92,6 +95,8 @@ export class MetricsManager {
this.actuallySendMetrics = gatherUsageStats

if (this.actuallySendMetrics || IS_SHARED_REPORT) {
// Segment will not initialize if this is rendered with SSR
initializeSegment()
// Only record the user's email if they entered a non-empty one.
const userTraits: any = {}
if (SessionInfo.current.authorEmail !== "") {
Expand Down
96 changes: 96 additions & 0 deletions frontend/src/lib/Segment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
declare global {
interface Window {
analytics: any
}
}
/** @function initializeSegment
* Loads the global analytics service provided segment.io
* @see {@link https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/quickstart/#}
* @version 4.1.0
*/
export const initializeSegment = (): void => {
// Create a queue, but don't obliterate an existing one!
const analytics = (window.analytics = window.analytics || [])

// If the real analytics.js is already on the page return.
if (analytics.initialize) return

// If the snippet was invoked already show an error.
if (analytics.invoked) {
if (window.console && console.error) {
console.error("Segment snippet included twice.")
}
return
}

// Invoked flag, to make sure the snippet
// is never invoked twice.
analytics.invoked = true

// A list of the methods in Analytics.js to stub.
analytics.methods = [
"trackSubmit",
"trackClick",
"trackLink",
"trackForm",
"pageview",
"identify",
"reset",
"group",
"track",
"ready",
"alias",
"debug",
"page",
"once",
"off",
"on",
"addSourceMiddleware",
"addIntegrationMiddleware",
"setAnonymousId",
"addDestinationMiddleware",
]

// Define a factory to create stubs. These are placeholders
// for methods in Analytics.js so that you never have to wait
// for it to load to actually record data. The `method` is
// stored as the first argument, so we can replay the data.
analytics.factory = function(method: any) {
return function(...args: any[]) {
const _args = Array.prototype.slice.call(args)
_args.unshift(method)
analytics.push(_args)
return analytics
}
}

// For each of our methods, generate a queueing stub.
for (let i = 0; i < analytics.methods.length; i++) {
const key = analytics.methods[i]
analytics[key] = analytics.factory(key)
}

// Define a method to load Analytics.js from our CDN,
// and that will be sure to only ever load it once.
analytics.load = function(key: string, options: any) {
// Create an async script element based on your key.
const script = document.createElement("script")
script.type = "text/javascript"
script.async = true
script.src =
"https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js"

// Insert our script next to the first script element.
const first = document.getElementsByTagName("script")[0]
if (first && first.parentNode) first.parentNode.insertBefore(script, first)
analytics._loadOptions = options
}

// Add a version to keep track of what's in the wild.
analytics.SNIPPET_VERSION = "4.1.0"

// Load Analytics.js with your key, which will automatically
// load the tools you've enabled for your account. Boosh!

analytics.load("iCkMy7ymtJ9qYzQRXkQpnAJEq7D4NyMU")
}

0 comments on commit 7cd06d5

Please sign in to comment.