Skip to content

Commit

Permalink
Merge branch 'master' into feat/usage-report-celery-queue
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite committed Jan 31, 2024
2 parents 52a6b03 + 6a840f4 commit cd901b4
Show file tree
Hide file tree
Showing 51 changed files with 5,973 additions and 272 deletions.
5 changes: 5 additions & 0 deletions cypress/e2e/featureFlags.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ describe('Feature Flags', () => {
cy.get('[data-attr=prop-val]').click()
cy.get('[data-attr=prop-val-0]').click({ force: true })

// set rollout percentage
cy.get('[data-attr=rollout-percentage]').clear().type('0').should('have.value', '0')

// save the feature flag
cy.get('[data-attr=save-feature-flag]').first().click()

Expand All @@ -65,6 +68,7 @@ describe('Feature Flags', () => {
.click()
.type(`{moveToEnd}-updated`)
.should('have.value', name + '-updated')
cy.get('[data-attr=rollout-percentage]').type('{selectall}50').should('have.value', '50')
cy.get('[data-attr=save-feature-flag]').first().click()
cy.wait(100)
cy.clickNavMenu('featureflags')
Expand All @@ -81,6 +85,7 @@ describe('Feature Flags', () => {
cy.get('[data-attr=top-bar-name]').should('contain', 'Feature flags')
cy.get('[data-attr=new-feature-flag]').click()
cy.get('[data-attr=feature-flag-key]').focus().type(name).should('have.value', name)
cy.get('[data-attr=rollout-percentage]').type('{selectall}50').should('have.value', '50')
cy.get('[data-attr=save-feature-flag]').first().click()

// after save there should be a delete button
Expand Down
2 changes: 1 addition & 1 deletion ee/clickhouse/models/test/__snapshots__/test_property.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# ---
# name: test_parse_groups_persons_edge_case_with_single_filter
tuple(
'AND ( has(%(vglobalperson_0)s, replaceRegexpAll(JSONExtractRaw(person_props, %(kglobalperson_0)s), \'^"|"$\', \'\')))',
'AND ( has(%(vglobalperson_0)s, "pmat_email"))',
dict({
'kglobalperson_0': 'email',
'vglobalperson_0': list([
Expand Down
26 changes: 26 additions & 0 deletions ee/clickhouse/queries/experiments/funnel_experiment_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ def __init__(

def get_results(self):
funnel_results = self.funnel.run()

validate_event_variants(funnel_results, self.variants)

filtered_results = [result for result in funnel_results if result[0]["breakdown_value"][0] in self.variants]

control_variant, test_variants = self.get_variants(filtered_results)

probabilities = self.calculate_results(control_variant, test_variants)
Expand Down Expand Up @@ -292,3 +296,25 @@ def calculate_probability_of_winning_for_each(variants: List[Variant]) -> List[P
total_test_probabilities = sum(probabilities[1:])

return [max(0, 1 - total_test_probabilities), *probabilities[1:]]


def validate_event_variants(funnel_results, variants):
if not funnel_results or not funnel_results[0]:
raise ValidationError("No experiment events have been ingested yet.", code="no-events")

eventsWithOrderZero = []
for eventArr in funnel_results:
for event in eventArr:
if event.get("order") == 0:
eventsWithOrderZero.append(event)

missing_variants = set(variants)
for event in eventsWithOrderZero:
event_variant = event.get("breakdown_value")[0]
if event_variant in missing_variants:
missing_variants.discard(event_variant)

if not len(missing_variants) == 0:
missing_variants_str = ", ".join(missing_variants)
message = f"No experiment events have been ingested yet for the following variants: {missing_variants_str}"
raise ValidationError(message, code=f"missing-flag-variants::{missing_variants_str}")
88 changes: 88 additions & 0 deletions ee/clickhouse/queries/test/test_experiments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import unittest
from ee.clickhouse.queries.experiments.funnel_experiment_result import validate_event_variants
from rest_framework.exceptions import ValidationError


class TestExperiments(unittest.TestCase):
def test_validate_event_variants_no_events(self):
expected_code = "no-events"
with self.assertRaises(ValidationError) as context:
validate_event_variants([], ["test", "control"])

self.assertEqual(expected_code, context.exception.detail[0].code)

def test_validate_event_variants_missing_variants(self):
funnel_results = [
[
{
"action_id": "step-a-1",
"name": "step-a-1",
"custom_name": None,
"order": 0,
"people": [],
"count": 1,
"type": "events",
"average_conversion_time": None,
"median_conversion_time": None,
"breakdown": ["test"],
"breakdown_value": ["test"],
},
{
"action_id": "step-a-2",
"name": "step-a-2",
"custom_name": None,
"order": 1,
"people": [],
"count": 0,
"type": "events",
"average_conversion_time": None,
"median_conversion_time": None,
"breakdown": ["test"],
"breakdown_value": ["test"],
},
]
]

expected_code = "missing-flag-variants::control"
with self.assertRaises(ValidationError) as context:
validate_event_variants(funnel_results, ["test", "control"])

self.assertEqual(expected_code, context.exception.detail[0].code)

def test_validate_event_variants_ignore_old_variant(self):
funnel_results = [
[
{
"action_id": "step-a-1",
"name": "step-a-1",
"custom_name": None,
"order": 0,
"people": [],
"count": 1,
"type": "events",
"average_conversion_time": None,
"median_conversion_time": None,
"breakdown": ["test"],
"breakdown_value": ["test"],
},
{
"action_id": "step-a-2",
"name": "step-a-2",
"custom_name": None,
"order": 1,
"people": [],
"count": 0,
"type": "events",
"average_conversion_time": None,
"median_conversion_time": None,
"breakdown": ["old-variant"],
"breakdown_value": ["old-variant"],
},
]
]

expected_code = "missing-flag-variants::control"
with self.assertRaises(ValidationError) as context:
validate_event_variants(funnel_results, ["test", "control"])

self.assertEqual(expected_code, context.exception.detail[0].code)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# serializer version: 1
# name: ClickhouseTestExperimentSecondaryResults.test_basic_secondary_metric_results
'''
/* user_id:132 celery:posthog.tasks.tasks.sync_insight_caching_state */
/* user_id:129 celery:posthog.tasks.tasks.sync_insight_caching_state */
SELECT team_id,
date_diff('second', max(timestamp), now()) AS age
FROM events
Expand Down
46 changes: 43 additions & 3 deletions ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,49 @@ exports[`replay/transform transform can convert navigation bar 1`] = `
{
"attributes": {
"data-rrweb-id": 12345,
"style": "border-width: 4px;border-radius: 10px;border-color: #ee3ee4;border-style: solid;color: #ee3ee4;width: 100px;height: 30px;position: fixed;left: 11px;top: 12px;",
"style": "border-width: 4px;border-radius: 10px;border-color: #ee3ee4;border-style: solid;color: #ee3ee4;width: 100px;height: 30px;position: fixed;left: 11px;top: 12px;display:flex;flex-direction:row;align-items:center;justify-content:space-around;color:white;",
},
"childNodes": [],
"childNodes": [
{
"attributes": {},
"childNodes": [
{
"id": 101,
"textContent": "",
"type": 3,
},
],
"id": 100,
"tagName": "div",
"type": 2,
},
{
"attributes": {},
"childNodes": [
{
"id": 103,
"textContent": "",
"type": 3,
},
],
"id": 102,
"tagName": "div",
"type": 2,
},
{
"attributes": {},
"childNodes": [
{
"id": 105,
"textContent": "⬜️",
"type": 3,
},
],
"id": 104,
"tagName": "div",
"type": 2,
},
],
"id": 12345,
"tagName": "div",
"type": 2,
Expand Down Expand Up @@ -428,7 +468,7 @@ exports[`replay/transform transform can convert status bar 1`] = `
{
"attributes": {
"data-rrweb-id": 12,
"style": "width: 100px;height: 0px;position: fixed;left: 13px;top: 17px;display:flex;flex-direction:row;align-items:center;",
"style": "color: black;width: 100px;height: 0px;position: fixed;left: 13px;top: 17px;display:flex;flex-direction:row;align-items:center;",
},
"childNodes": [
{
Expand Down
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
}
38 changes: 35 additions & 3 deletions ee/frontend/mobile-replay/transformer/screen-chrome.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
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'
Expand All @@ -17,22 +18,50 @@ function spacerDiv(idSequence: Generator<number>): serializedNodeWithId {
}
}

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)]),
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: [],
childNodes: [backArrowTriangle, homeCircle, screenButton],
},
context,
}
Expand All @@ -51,6 +80,9 @@ export function makeStatusBar(
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',
Expand All @@ -73,7 +105,7 @@ export function makeStatusBar(
tagName: 'div',
attributes: {
style: asStyleString([
makeStylesString(wireframe),
makeStylesString(wireframe, { color: clockFontColor }),
'display:flex',
'flex-direction:row',
'align-items:center',
Expand Down
6 changes: 3 additions & 3 deletions ee/tasks/subscriptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def _deliver_subscription_report(
subscription.save()


@shared_task(queue=CeleryQueue.SUBSCRIPTION_DELIVERY)
@shared_task(queue=CeleryQueue.SUBSCRIPTION_DELIVERY.value)
def schedule_all_subscriptions() -> None:
"""
Schedule all past notifications (with a buffer) to be delivered
Expand Down Expand Up @@ -152,7 +152,7 @@ def schedule_all_subscriptions() -> None:
@shared_task(
soft_time_limit=report_timeout_seconds,
time_limit=report_timeout_seconds + 10,
queue=CeleryQueue.SUBSCRIPTION_DELIVERY,
queue=CeleryQueue.SUBSCRIPTION_DELIVERY.value,
)
def deliver_subscription_report(subscription_id: int) -> None:
return _deliver_subscription_report(subscription_id)
Expand All @@ -161,7 +161,7 @@ def deliver_subscription_report(subscription_id: int) -> None:
@shared_task(
soft_time_limit=report_timeout_seconds,
time_limit=report_timeout_seconds + 10,
queue=CeleryQueue.SUBSCRIPTION_DELIVERY,
queue=CeleryQueue.SUBSCRIPTION_DELIVERY.value,
)
def handle_subscription_value_change(
subscription_id: int, previous_value: str, invite_message: Optional[str] = None
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/JSSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function JSSnippet(): JSX.Element {
return (
<CodeSnippet language={Language.HTML}>{`<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 getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys onSessionId".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('${currentTeam?.api_token}',{api_host:'${apiHostOrigin}'})
posthog.init('${currentTeam?.api_token}',{api_host:'${apiHostOrigin()}'})
</script>`}</CodeSnippet>
)
}
Loading

0 comments on commit cd901b4

Please sign in to comment.