Skip to content

Commit

Permalink
Merge branch 'master' into fix-feedback-btn
Browse files Browse the repository at this point in the history
  • Loading branch information
liyiy committed Jan 9, 2024
2 parents 3b86161 + 2692c59 commit 7ac4bb4
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 7 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.97.0 - 2024-01-09

- fix: add a comment explaining browser type prop (#952)
- feat: add opt_out_useragent_filter and $browser_type (#949)
- chore(surveys): add basic survey e2e tests (#948)
- Tidying and removing of old value (#941)

## 1.96.1 - 2023-12-15

- Add gas_source to campaign params (#934)
Expand Down
197 changes: 197 additions & 0 deletions cypress/e2e/surveys.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/// <reference types="cypress" />
import { getBase64EncodedPayload } from '../support/compression'

function onPageLoad() {
cy.posthogInit(given.options)
cy.wait('@decide')
cy.wait('@surveys')
}

describe('Surveys', () => {
given('options', () => ({}))
beforeEach(() => {
cy.intercept('POST', '**/decide/*', {
config: { enable_collect_everything: false },
editorParams: {},
surveys: true,
isAuthenticated: false,
}).as('decide')
})
it('shows and submits a basic survey', () => {
cy.intercept('GET', '**/surveys/*', {
surveys: [
{
id: '123',
name: 'Test survey',
active: true,
type: 'popover',
start_date: '2021-01-01T00:00:00Z',
questions: [{ type: 'open', question: 'What is your role?', description: 'test description' }],
},
],
}).as('surveys')
cy.visit('./playground/cypress')
onPageLoad()
cy.wait(500)
const survey = cy.get('.PostHogSurvey123').shadow()
survey.find('.survey-123-form').should('be.visible')
cy.get('.PostHogSurvey123').shadow().find('.survey-question').should('have.text', 'What is your role?')
cy.get('.PostHogSurvey123').shadow().find('.description').should('have.text', 'test description')
survey.find('.question-textarea-wrapper').type('product engineer')
cy.get('.PostHogSurvey123').shadow().find('.form-submit').click()
cy.phCaptures().should('include', 'survey sent')
})

it('shows confirmation message after submitting', () => {
cy.intercept('GET', '**/surveys/*', {
surveys: [
{
id: '1234',
name: 'Test survey 2',
active: true,
type: 'popover',
start_date: '2021-01-01T00:00:00Z',
questions: [
{ type: 'rating', display: 'number', scale: 10, question: 'Would you recommend surveys?' },
],
appearance: {
displayThankYouMessage: true,
thankyouMessageHeader: 'Thanks!',
thankyouMessageBody: 'We appreciate your feedback.',
},
},
],
}).as('surveys')
cy.visit('./playground/cypress')
onPageLoad()
cy.get('.PostHogSurvey1234').shadow().find('.ratings-number').should('be.visible')
cy.get('.PostHogSurvey1234').shadow().find('.ratings-number').first().click()
cy.get('.PostHogSurvey1234').shadow().find('.form-submit').click()
expect(cy.get('.PostHogSurvey1234').shadow().find('.thank-you-message').should('be.visible'))
})

it('multiple question surveys', () => {
cy.intercept('GET', '**/surveys/*', {
surveys: [
{
id: '12345',
name: 'multiple question survey',
active: true,
type: 'popover',
start_date: '2021-01-01T00:00:00Z',
questions: [
{
question: 'Which types of content would you like to see more of?',
description: 'This is a question description',
type: 'multiple_choice',
choices: ['Tutorials', 'Product Updates', 'Events', 'Other'],
},
{ type: 'open', question: 'Why?' },
{
type: 'rating',
display: 'emoji',
scale: 5,
question: 'How does this survey make you feel?',
optional: true,
},
{
type: 'link',
question: 'Would you like to participate in a user study?',
link: 'https://posthog.com',
buttonText: 'Yes',
},
],
appearance: {
displayThankYouMessage: true,
thankyouMessageHeader: 'Thanks!',
thankyouMessageBody: 'We appreciate your feedback.',
},
},
],
}).as('surveys')
cy.intercept('POST', '**/e/*').as('capture-assertion')
cy.visit('./playground/cypress')
onPageLoad()
cy.wait(500)
cy.get('.PostHogSurvey12345').shadow().find('.survey-12345-form').should('be.visible')
cy.get('.PostHogSurvey12345').shadow().find('#surveyQuestion0Choice1').click()
cy.get('.PostHogSurvey12345').shadow().find('#surveyQuestion0Choice2').click()
cy.get('.PostHogSurvey12345').shadow().find('.form-submit').eq(0).click()
cy.get('.PostHogSurvey12345')
.shadow()
.find('.question-textarea-wrapper')
.first()
.type('Because I want to learn more about PostHog')
cy.get('.PostHogSurvey12345').shadow().find('.form-submit').eq(1).click()
cy.get('.PostHogSurvey12345').shadow().find('.form-submit').eq(2).click()
cy.get('.PostHogSurvey12345').shadow().find('.form-submit').eq(3).click()
cy.wait('@capture-assertion')
cy.wait('@capture-assertion').then(async ({ request }) => {
const captures = await getBase64EncodedPayload(request)
expect(captures.map(({ event }) => event)).to.deep.equal(['survey shown', 'survey sent'])
expect(captures[1].properties['$survey_response']).to.deep.equal(['Product Updates', 'Events'])
expect(captures[1].properties).to.contain({
$survey_id: '12345',
$survey_response_1: 'Because I want to learn more about PostHog',
$survey_response_2: null,
$survey_response_3: 'link clicked',
})
})
expect(cy.get('.PostHogSurvey12345').shadow().find('.thank-you-message').should('be.visible'))
})

describe('survey response capture', () => {
it('captures survey shown and survey dismissed events', () => {
cy.visit('./playground/cypress')
cy.intercept('GET', '**/surveys/*', {
surveys: [
{
id: '123',
name: 'Test survey',
description: 'description',
active: true,
type: 'popover',
start_date: '2021-01-01T00:00:00Z',
questions: [{ type: 'open', question: 'What is a survey event capture test?' }],
},
],
}).as('surveys')
cy.intercept('POST', '**/e/*').as('capture-assertion')
onPageLoad()
// first capture is $pageview
cy.wait('@capture-assertion')
cy.get('.PostHogSurvey123').shadow().find('.cancel-btn-wrapper').click()
cy.wait('@capture-assertion').then(async ({ request }) => {
const captures = await getBase64EncodedPayload(request)
expect(captures.map(({ event }) => event)).to.deep.equal(['survey shown', 'survey dismissed'])
})
})

it('captures survey sent event', () => {
cy.visit('./playground/cypress')
cy.intercept('GET', '**/surveys/*', {
surveys: [
{
id: '123',
name: 'Test survey',
description: 'description',
active: true,
type: 'popover',
start_date: '2021-01-01T00:00:00Z',
questions: [{ type: 'open', question: 'What is a survey event capture test?' }],
},
],
}).as('surveys')
cy.intercept('POST', '**/e/*').as('capture-assertion')
onPageLoad()
cy.get('.PostHogSurvey123').shadow().find('.question-textarea-wrapper').type('product engineer')
cy.get('.PostHogSurvey123').shadow().find('.form-submit').click()
cy.wait('@capture-assertion')
cy.wait('@capture-assertion').then(async ({ request }) => {
const captures = await getBase64EncodedPayload(request)
expect(captures.map(({ event }) => event)).to.deep.equal(['survey shown', 'survey sent'])
expect(captures[1].properties).to.contain({ $survey_id: '123', $survey_response: 'product engineer' })
})
})
})
})
5 changes: 5 additions & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ beforeEach(() => {
cy.intercept('POST', '**/decide/*').as('decide')
cy.intercept('POST', '**/e/*').as('capture')
cy.intercept('POST', '**/ses/*').as('session-recording')
cy.intercept('GET', '**/surveys/*').as('surveys')

cy.readFile('dist/array.full.js').then((body) => {
cy.intercept('**/static/array.full.js', { body })
Expand All @@ -45,4 +46,8 @@ beforeEach(() => {
cy.readFile('dist/recorder.js').then((body) => {
cy.intercept('**/static/recorder.js*', { body }).as('recorder')
})

cy.readFile('dist/surveys.js').then((body) => {
cy.intercept('**/static/surveys.js*', { body })
})
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-js",
"version": "1.96.1",
"version": "1.97.0",
"description": "Posthog-js allows you to automatically capture usage and send events to PostHog.",
"repository": "https://github.com/PostHog/posthog-js",
"author": "[email protected]",
Expand Down
2 changes: 1 addition & 1 deletion playground/cypress-full/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<br />

<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.full.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.full.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getSurveys getActiveMatchingSurveys".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
</script>
</body>
</html>
6 changes: 5 additions & 1 deletion playground/cypress/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
Test a feature flag
</button>

<button class="test-surveys" onclick="posthog.getActiveMatchingSurveys(surveys => console.log(surveys))">
Test surveys
</button>

<br />

<div data-cy-captures>
Expand All @@ -37,7 +41,7 @@
</button>

<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getSurveys getActiveMatchingSurveys".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
</script>
</body>
</html>
39 changes: 39 additions & 0 deletions src/__tests__/posthog-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { autocapture } from '../autocapture'
import { truth } from './helpers/truth'
import { _info } from '../utils/event-utils'
import { document, window } from '../utils/globals'
import * as globals from '../utils/globals'

jest.mock('../gdpr-utils', () => ({
...jest.requireActual('../gdpr-utils'),
Expand Down Expand Up @@ -127,6 +128,44 @@ describe('posthog core', () => {
expect(console.error).toHaveBeenCalledWith('[PostHog.js]', 'No event name provided to posthog.capture')
})

it('respects opt_out_useragent_filter (default: false)', () => {
const originalUseragent = globals.userAgent
// eslint-disable-next-line no-import-assign
globals['userAgent'] =
'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/W.X.Y.Z Safari/537.36'

const hook = jest.fn()
given.lib._addCaptureHook(hook)
given.subject()
expect(hook).not.toHaveBeenCalledWith('$event')

// eslint-disable-next-line no-import-assign
globals['userAgent'] = originalUseragent
})

it('respects opt_out_useragent_filter', () => {
const originalUseragent = globals.userAgent

given('config', () => ({
opt_out_useragent_filter: true,
property_blacklist: [],
_onCapture: jest.fn(),
}))

// eslint-disable-next-line no-import-assign
globals['userAgent'] =
'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/W.X.Y.Z Safari/537.36'

const hook = jest.fn()
given.lib._addCaptureHook(hook)
const event = given.subject()
expect(hook).toHaveBeenCalledWith('$event')
expect(event.properties['$browser_type']).toEqual('bot')

// eslint-disable-next-line no-import-assign
globals['userAgent'] = originalUseragent
})

it('truncates long properties', () => {
given('config', () => ({
properties_string_max_length: 1000,
Expand Down
18 changes: 17 additions & 1 deletion src/posthog-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export const defaultConfig = (): PostHogConfig => ({
ip: true,
opt_out_capturing_by_default: false,
opt_out_persistence_by_default: false,
opt_out_useragent_filter: false,
opt_out_capturing_persistence_type: 'localStorage',
opt_out_capturing_cookie_prefix: null,
opt_in_site_apps: false,
Expand Down Expand Up @@ -866,7 +867,11 @@ export class PostHog {
return
}

if (userAgent && _isBlockedUA(userAgent, this.config.custom_blocked_useragents)) {
if (
userAgent &&
!this.config.opt_out_useragent_filter &&
_isBlockedUA(userAgent, this.config.custom_blocked_useragents)
) {
return
}

Expand Down Expand Up @@ -989,6 +994,14 @@ export class PostHog {
properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3))
}

// this is only added when this.config.opt_out_useragent_filter is true,
// or it would always add "browser"
if (userAgent && this.config.opt_out_useragent_filter) {
properties['$browser_type'] = _isBlockedUA(userAgent, this.config.custom_blocked_useragents)
? 'bot'
: 'browser'
}

// note: extend writes to the first object, so lets make sure we
// don't write to the persistence properties object and info
// properties object by passing in a new object
Expand Down Expand Up @@ -1631,6 +1644,9 @@ export class PostHog {
* // opt users out of browser data storage by this PostHog instance by default
* opt_out_persistence_by_default: false
*
* // opt out of user agent filtering such as googlebot or other bots
* opt_out_useragent_filter: false
*
* // persistence mechanism used by opt-in/opt-out methods - cookie
* // or localStorage - falls back to cookie if localStorage is unavailable
* opt_out_capturing_persistence_type: 'localStorage'
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ export interface PostHogConfig {
ip: boolean
opt_out_capturing_by_default: boolean
opt_out_persistence_by_default: boolean
/** Opt out of user agent filtering such as googlebot or other bots. Defaults to `false` */
opt_out_useragent_filter: boolean
opt_out_capturing_persistence_type: 'localStorage' | 'cookie'
opt_out_capturing_cookie_prefix: string | null
opt_in_site_apps: boolean
Expand Down
6 changes: 3 additions & 3 deletions src/utils/blocked-uas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export const DEFAULT_BLOCKED_UA_STRS = [
'storebot-google',
]

// _.isBlockedUA()
// This is to block various web spiders from executing our JS and
// sending false capturing data
/**
* Block various web spiders from executing our JS and sending false capturing data
*/
export const _isBlockedUA = function (ua: string, customBlockedUserAgents: string[]): boolean {
if (!ua) {
return false
Expand Down

0 comments on commit 7ac4bb4

Please sign in to comment.