Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into by/dont-drop-data-r…
Browse files Browse the repository at this point in the history
…etention
  • Loading branch information
Bianca Yang committed Jan 30, 2024
2 parents 51a95d8 + c679836 commit 39a1668
Show file tree
Hide file tree
Showing 121 changed files with 3,782 additions and 1,154 deletions.
3 changes: 2 additions & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { withMockDate } from './decorators/withMockDate'
import { defaultMocks } from '~/mocks/handlers'
import { withFeatureFlags } from './decorators/withFeatureFlags'
import { withTheme } from './decorators/withTheme'
import { apiHostOrigin } from 'lib/utils/apiHost'

const setupMsw = () => {
// Make sure the msw worker is started
Expand All @@ -35,7 +36,7 @@ setupMsw()
const setupPosthogJs = () => {
// Make sure we don't hit production posthog. We want to control requests to,
// e.g. `/decide/` for feature flags
window.JS_POSTHOG_HOST = window.location.origin
window.JS_POSTHOG_HOST = apiHostOrigin()

loadPostHogJS()
}
Expand Down
1,582 changes: 1,384 additions & 198 deletions ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions ee/frontend/mobile-replay/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import { ifEeDescribe } from 'lib/ee.test'
import { PostHogEE } from '../../../frontend/@posthog/ee/types'
import * as incrementalSnapshotJson from './__mocks__/increment-with-child-duplication.json'
import { validateAgainstWebSchema, validateFromMobile } from './index'
import { wireframe } from './mobile.types'
import { stripBarsFromWireframes } from './transformer/transformers'

const unspecifiedBase64ImageURL =
'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII='

const heartEyesEmojiURL = 'data:image/png;base64,' + unspecifiedBase64ImageURL

function fakeWireframe(type: string, children?: wireframe[]): wireframe {
// this is a fake so we can force the type
return { type, childWireframes: children || [] } as Partial<wireframe> as wireframe
}

describe('replay/transform', () => {
describe('validation', () => {
test('example of validating incoming _invalid_ data', () => {
Expand Down Expand Up @@ -1050,4 +1057,40 @@ describe('replay/transform', () => {
})
})
})

describe('separate status and navbar from other wireframes', () => {
it('no-op', () => {
expect(stripBarsFromWireframes([])).toEqual({
appNodes: [],
statusBar: undefined,
navigationBar: undefined,
})
})

it('top-level status-bar', () => {
const statusBar = fakeWireframe('status_bar')
expect(stripBarsFromWireframes([statusBar])).toEqual({ appNodes: [], statusBar, navigationBar: undefined })
})

it('top-level nav-bar', () => {
const navBar = fakeWireframe('navigation_bar')
expect(stripBarsFromWireframes([navBar])).toEqual({
appNodes: [],
statusBar: undefined,
navigationBar: navBar,
})
})

it('nested nav-bar', () => {
const navBar = fakeWireframe('navigation_bar')
const sourceWithNavBar = [
fakeWireframe('div', [fakeWireframe('div'), fakeWireframe('div', [navBar, fakeWireframe('div')])]),
]
expect(stripBarsFromWireframes(sourceWithNavBar)).toEqual({
appNodes: [fakeWireframe('div', [fakeWireframe('div'), fakeWireframe('div', [fakeWireframe('div')])])],
statusBar: undefined,
navigationBar: navBar,
})
})
})
})
51 changes: 51 additions & 0 deletions ee/frontend/mobile-replay/transformer/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// from https://gist.github.com/t1grok/a0f6d04db569890bcb57

interface rgb {
r: number
g: number
b: number
}
interface yuv {
y: number
u: number
v: number
}

function hexToRgb(hexColor: string): rgb | null {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hexColor = hexColor.replace(shorthandRegex, function (_, r, g, b) {
return r + r + g + g + b + b
})

const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor)
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null
}

function rgbToYuv(rgbColor: rgb): yuv {
let y, u, v

y = rgbColor.r * 0.299 + rgbColor.g * 0.587 + rgbColor.b * 0.114
u = rgbColor.r * -0.168736 + rgbColor.g * -0.331264 + rgbColor.b * 0.5 + 128
v = rgbColor.r * 0.5 + rgbColor.g * -0.418688 + rgbColor.b * -0.081312 + 128

y = Math.floor(y)
u = Math.floor(u)
v = Math.floor(v)

return { y: y, u: u, v: v }
}

export const isLight = (hexColor: string): boolean => {
const rgbColor = hexToRgb(hexColor)
if (!rgbColor) {
return false
}
const yuvColor = rgbToYuv(rgbColor)
return yuvColor.y > 128
}
120 changes: 120 additions & 0 deletions ee/frontend/mobile-replay/transformer/screen-chrome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { NodeType, serializedNodeWithId, wireframeNavigationBar, wireframeStatusBar } from '../mobile.types'
import { isLight } from './colors'
import { NAVIGATION_BAR_ID, STATUS_BAR_ID } from './transformers'
import { ConversionContext, ConversionResult } from './types'
import { asStyleString, makeStylesString } from './wireframeStyle'

function spacerDiv(idSequence: Generator<number>): serializedNodeWithId {
const spacerId = idSequence.next().value
return {
type: NodeType.Element,
tagName: 'div',
attributes: {
style: 'width: 5px;',
'data-rrweb-id': spacerId,
},
id: spacerId,
childNodes: [],
}
}

function makeFakeNavButton(icon: string, context: ConversionContext): serializedNodeWithId {
return {
type: NodeType.Element,
tagName: 'div',
attributes: {},
id: context.idSequence.next().value,
childNodes: [
{
type: NodeType.Text,
textContent: icon,
id: context.idSequence.next().value,
},
],
}
}

export function makeNavigationBar(
wireframe: wireframeNavigationBar,
_children: serializedNodeWithId[],
context: ConversionContext
): ConversionResult<serializedNodeWithId> | null {
const _id = wireframe.id || NAVIGATION_BAR_ID

const backArrowTriangle = makeFakeNavButton('◀', context)
const homeCircle = makeFakeNavButton('⚪', context)
const screenButton = makeFakeNavButton('⬜️', context)

return {
result: {
type: NodeType.Element,
tagName: 'div',
attributes: {
style: asStyleString([
makeStylesString(wireframe),
'display:flex',
'flex-direction:row',
'align-items:center',
'justify-content:space-around',
'color:white',
]),
'data-rrweb-id': _id,
},
id: _id,
childNodes: [backArrowTriangle, homeCircle, screenButton],
},
context,
}
}

/**
* tricky: we need to accept children because that's the interface of converters, but we don't use them
*/
export function makeStatusBar(
wireframe: wireframeStatusBar,
_children: serializedNodeWithId[],
context: ConversionContext
): ConversionResult<serializedNodeWithId> {
const clockId = context.idSequence.next().value
// convert the wireframe timestamp to a date time, then get just the hour and minute of the time from that
const clockTime = context.timestamp
? new Date(context.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
: ''

const clockFontColor = isLight(wireframe.style?.backgroundColor || '#ffffff') ? 'black' : 'white'

const clock: serializedNodeWithId = {
type: NodeType.Element,
tagName: 'div',
attributes: {
'data-rrweb-id': clockId,
},
id: clockId,
childNodes: [
{
type: NodeType.Text,
textContent: clockTime,
id: context.idSequence.next().value,
},
],
}

return {
result: {
type: NodeType.Element,
tagName: 'div',
attributes: {
style: asStyleString([
makeStylesString(wireframe, { color: clockFontColor }),
'display:flex',
'flex-direction:row',
'align-items:center',
]),
'data-rrweb-id': STATUS_BAR_ID,
},
id: STATUS_BAR_ID,
childNodes: [spacerDiv(context.idSequence), clock],
},
context,
}
}
56 changes: 0 additions & 56 deletions ee/frontend/mobile-replay/transformer/status-bar.ts

This file was deleted.

Loading

0 comments on commit 39a1668

Please sign in to comment.