diff --git a/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx b/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx
index f31e3e047ff1e..5bc75ec9aebbd 100644
--- a/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx
+++ b/frontend/src/lib/components/AuthorizedUrlList/AuthorizedUrlList.tsx
@@ -193,6 +193,11 @@ export function AuthorizedUrlList({
}
center
data-attr="toolbar-open"
+ disabledReason={
+ keyedURL.url.includes('*')
+ ? 'Wildcard domains cannot be launched'
+ : undefined
+ }
>
Launch
diff --git a/frontend/src/scenes/experiments/ExperimentCodeSnippets.tsx b/frontend/src/scenes/experiments/ExperimentCodeSnippets.tsx
index f4513affb6556..d936d1531a126 100644
--- a/frontend/src/scenes/experiments/ExperimentCodeSnippets.tsx
+++ b/frontend/src/scenes/experiments/ExperimentCodeSnippets.tsx
@@ -20,6 +20,36 @@ interface SnippetProps {
variant: string
}
+export function AndroidSnippet({ flagKey, variant }: SnippetProps): JSX.Element {
+ return (
+ <>
+
+ {`if (PostHog.getFeatureFlag("${flagKey}") == "${variant}") {
+ // do something
+} else {
+ // It's a good idea to let control variant always be the default behaviour,
+ // so if something goes wrong with flag evaluation, you don't break your app.
+}`}
+
+ >
+ )
+}
+
+export function IOSSnippet({ flagKey, variant }: SnippetProps): JSX.Element {
+ return (
+ <>
+
+ {`if (PostHogSDK.shared.getFeatureFlag("${flagKey}") as? String == "${variant}") {
+ // do something
+} else {
+ // It's a good idea to let control variant always be the default behaviour,
+ // so if something goes wrong with flag evaluation, you don't break your app.
+}`}
+
+ >
+ )
+}
+
export function NodeJSSnippet({ flagKey, variant }: SnippetProps): JSX.Element {
return (
<>
@@ -59,6 +89,43 @@ export function JSSnippet({ flagKey, variant }: SnippetProps): JSX.Element {
)
}
+export function ReactSnippet({ flagKey, variant }: SnippetProps): JSX.Element {
+ return (
+ <>
+
+ {`// You can either use the useFeatureFlagVariantKey hook,
+// or you can use the feature flags component - https://posthog.com/docs/libraries/react#feature-flags-react-component
+
+// Method one: using the useFeatureFlagVariantKey hook
+import { useFeatureFlagVariantKey } from 'posthog-js/react'
+
+function App() {
+ const variant = useFeatureFlagVariantKey('${flagKey}')
+ if (variant === '${variant}') {
+ // do something
+ }
+}
+
+// Method two: using the feature flags component
+import { PostHogFeature } from 'posthog-js/react'
+
+function App() {
+ return (
+
+
+ {/* the component to show */}
+
+
+ )
+}
+
+// You can also test your code by overriding the feature flag:
+posthog.featureFlags.override({'${flagKey}': '${variant}'})`}
+
+ >
+ )
+}
+
export function RNSnippet({ flagKey, variant }: SnippetProps): JSX.Element {
return (
<>
diff --git a/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx b/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx
index 8a836986ed37a..f8407b60582d1 100644
--- a/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx
+++ b/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx
@@ -1,15 +1,27 @@
import { LemonSelect, Link } from '@posthog/lemon-ui'
-import { IconGolang, IconJavascript, IconNodeJS, IconPHP, IconPython, IconRuby } from 'lib/lemon-ui/icons'
+import {
+ IconAndroidOS,
+ IconAppleIOS,
+ IconGolang,
+ IconJavascript,
+ IconNodeJS,
+ IconPHP,
+ IconPython,
+ IconRuby,
+} from 'lib/lemon-ui/icons'
import { useState } from 'react'
import { Experiment, MultivariateFlagVariant } from '~/types'
import {
+ AndroidSnippet,
GolangSnippet,
+ IOSSnippet,
JSSnippet,
NodeJSSnippet,
PHPSnippet,
PythonSnippet,
+ ReactSnippet,
RNSnippet,
RubySnippet,
} from './ExperimentCodeSnippets'
@@ -22,48 +34,81 @@ const UTM_TAGS = '?utm_medium=in-product&utm_campaign=experiment'
const DOC_BASE_URL = 'https://posthog.com/docs/'
const FF_ANCHOR = '#feature-flags'
+export enum LibraryType {
+ Client = 'Client',
+ Server = 'Server',
+}
+
const OPTIONS = [
{
value: 'JavaScript',
documentationLink: `${DOC_BASE_URL}libraries/js${UTM_TAGS}${FF_ANCHOR}`,
Icon: IconJavascript,
Snippet: JSSnippet,
+ type: LibraryType.Client,
},
{
- value: 'ReactNative',
- documentationLink: `${DOC_BASE_URL}libraries/react-native${UTM_TAGS}${FF_ANCHOR}`,
- Icon: IconJavascript,
- Snippet: RNSnippet,
+ value: 'Android',
+ documentationLink: `${DOC_BASE_URL}libraries/android${UTM_TAGS}${FF_ANCHOR}`,
+ Icon: IconAndroidOS,
+ Snippet: AndroidSnippet,
+ type: LibraryType.Client,
+ },
+ {
+ value: 'Go',
+ documentationLink: `${DOC_BASE_URL}libraries/go${UTM_TAGS}${FF_ANCHOR}`,
+ Icon: IconGolang,
+ Snippet: GolangSnippet,
+ type: LibraryType.Server,
+ },
+ {
+ value: 'iOS',
+ documentationLink: `${DOC_BASE_URL}libraries/ios${UTM_TAGS}${FF_ANCHOR}`,
+ Icon: IconAppleIOS,
+ Snippet: IOSSnippet,
+ type: LibraryType.Client,
},
{
value: 'Node.js',
documentationLink: `${DOC_BASE_URL}libraries/node${UTM_TAGS}${FF_ANCHOR}`,
Icon: IconNodeJS,
Snippet: NodeJSSnippet,
+ type: LibraryType.Server,
},
{
value: 'PHP',
documentationLink: `${DOC_BASE_URL}libraries/php${UTM_TAGS}${FF_ANCHOR}`,
Icon: IconPHP,
Snippet: PHPSnippet,
- },
- {
- value: 'Ruby',
- documentationLink: `${DOC_BASE_URL}libraries/ruby${UTM_TAGS}${FF_ANCHOR}`,
- Icon: IconRuby,
- Snippet: RubySnippet,
- },
- {
- value: 'Golang',
- documentationLink: `${DOC_BASE_URL}libraries/go${UTM_TAGS}${FF_ANCHOR}`,
- Icon: IconGolang,
- Snippet: GolangSnippet,
+ type: LibraryType.Server,
},
{
value: 'Python',
documentationLink: `${DOC_BASE_URL}libraries/python${UTM_TAGS}${FF_ANCHOR}`,
Icon: IconPython,
Snippet: PythonSnippet,
+ type: LibraryType.Server,
+ },
+ {
+ value: 'React',
+ documentationLink: `${DOC_BASE_URL}libraries/react${UTM_TAGS}${FF_ANCHOR}`,
+ Icon: IconJavascript,
+ Snippet: ReactSnippet,
+ type: LibraryType.Client,
+ },
+ {
+ value: 'React Native',
+ documentationLink: `${DOC_BASE_URL}libraries/react-native${UTM_TAGS}${FF_ANCHOR}`,
+ Icon: IconJavascript,
+ Snippet: RNSnippet,
+ type: LibraryType.Client,
+ },
+ {
+ value: 'Ruby',
+ documentationLink: `${DOC_BASE_URL}libraries/ruby${UTM_TAGS}${FF_ANCHOR}`,
+ Icon: IconRuby,
+ Snippet: RubySnippet,
+ type: LibraryType.Server,
},
]
@@ -80,16 +125,34 @@ export function CodeLanguageSelect({
className="min-w-[7.5rem]"
onSelect={selectOption}
value={selectedOptionValue}
- options={OPTIONS.map(({ value, Icon }) => ({
- value,
- label: value,
- labelInMenu: (
-
-
- {value}
-
- ),
- }))}
+ options={[
+ {
+ title: 'Client libraries',
+ options: OPTIONS.filter((option) => option.type == LibraryType.Client).map(({ Icon, value }) => ({
+ value,
+ label: value,
+ labelInMenu: (
+
+
+ {value}
+
+ ),
+ })),
+ },
+ {
+ title: 'Server libraries',
+ options: OPTIONS.filter((option) => option.type == LibraryType.Server).map(({ Icon, value }) => ({
+ value,
+ label: value,
+ labelInMenu: (
+
+
+ {value}
+
+ ),
+ })),
+ },
+ ]}
/>
)
}
diff --git a/posthog/hogql_queries/insights/funnels/test/test_funnel_strict.py b/posthog/hogql_queries/insights/funnels/test/test_funnel_strict.py
index dbc215fbb5f6a..2f1bf491be8a1 100644
--- a/posthog/hogql_queries/insights/funnels/test/test_funnel_strict.py
+++ b/posthog/hogql_queries/insights/funnels/test/test_funnel_strict.py
@@ -13,11 +13,11 @@
funnel_breakdown_group_test_factory,
assert_funnel_results_equal,
)
+from posthog.hogql_queries.insights.funnels.test.test_funnel import PseudoFunnelActors
from posthog.hogql_queries.legacy_compatibility.filter_to_query import filter_to_query
from posthog.models.action import Action
from posthog.models.filters import Filter
from posthog.models.instance_setting import override_instance_config
-from posthog.queries.funnels.funnel_strict_persons import ClickhouseFunnelStrictActors
from posthog.schema import FunnelsQuery
from posthog.test.base import (
APIBaseTest,
@@ -43,7 +43,7 @@ class BaseTestFunnelStrictStepsBreakdown(
ClickhouseTestMixin,
funnel_breakdown_test_factory( # type: ignore
FunnelOrderType.STRICT,
- ClickhouseFunnelStrictActors,
+ PseudoFunnelActors,
_create_action,
_create_person,
),
@@ -184,7 +184,7 @@ class BaseTestStrictFunnelGroupBreakdown(
ClickhouseTestMixin,
funnel_breakdown_group_test_factory( # type: ignore
FunnelOrderType.STRICT,
- ClickhouseFunnelStrictActors,
+ PseudoFunnelActors,
),
):
__test__ = False
@@ -192,7 +192,7 @@ class BaseTestStrictFunnelGroupBreakdown(
class BaseTestFunnelStrictStepsConversionTime(
ClickhouseTestMixin,
- funnel_conversion_time_test_factory(FunnelOrderType.ORDERED, ClickhouseFunnelStrictActors), # type: ignore
+ funnel_conversion_time_test_factory(FunnelOrderType.ORDERED, PseudoFunnelActors), # type: ignore
):
maxDiff = None
__test__ = False
@@ -205,7 +205,7 @@ class BaseTestFunnelStrictSteps(ClickhouseTestMixin, APIBaseTest):
def _get_actor_ids_at_step(self, filter, funnel_step, breakdown_value=None):
filter = Filter(data=filter, team=self.team)
person_filter = filter.shallow_clone({"funnel_step": funnel_step, "funnel_step_breakdown": breakdown_value})
- _, serialized_result, _ = ClickhouseFunnelStrictActors(person_filter, self.team).get_actors()
+ _, serialized_result, _ = PseudoFunnelActors(person_filter, self.team).get_actors()
return [val["id"] for val in serialized_result]
diff --git a/posthog/hogql_queries/insights/funnels/test/test_funnel_unordered.py b/posthog/hogql_queries/insights/funnels/test/test_funnel_unordered.py
index e7596024d8b83..8b1572d298237 100644
--- a/posthog/hogql_queries/insights/funnels/test/test_funnel_unordered.py
+++ b/posthog/hogql_queries/insights/funnels/test/test_funnel_unordered.py
@@ -5,14 +5,12 @@
from posthog.constants import INSIGHT_FUNNELS, FunnelOrderType
from posthog.hogql_queries.insights.funnels.funnels_query_runner import FunnelsQueryRunner
+from posthog.hogql_queries.insights.funnels.test.test_funnel import PseudoFunnelActors
from posthog.hogql_queries.legacy_compatibility.filter_to_query import filter_to_query
from posthog.models.action import Action
from posthog.models.filters import Filter
from posthog.models.property_definition import PropertyDefinition
-from posthog.queries.funnels.funnel_unordered_persons import (
- ClickhouseFunnelUnorderedActors,
-)
from posthog.hogql_queries.insights.funnels.test.conversion_time_cases import (
funnel_conversion_time_test_factory,
)
@@ -49,7 +47,7 @@ class TestFunnelUnorderedStepsBreakdown(
ClickhouseTestMixin,
funnel_breakdown_test_factory( # type: ignore
FunnelOrderType.UNORDERED,
- ClickhouseFunnelUnorderedActors,
+ PseudoFunnelActors,
_create_action,
_create_person,
),
@@ -638,7 +636,7 @@ class TestUnorderedFunnelGroupBreakdown(
ClickhouseTestMixin,
funnel_breakdown_group_test_factory( # type: ignore
FunnelOrderType.UNORDERED,
- ClickhouseFunnelUnorderedActors,
+ PseudoFunnelActors,
),
):
pass
@@ -648,7 +646,7 @@ class TestFunnelUnorderedStepsConversionTime(
ClickhouseTestMixin,
funnel_conversion_time_test_factory( # type: ignore
FunnelOrderType.UNORDERED,
- ClickhouseFunnelUnorderedActors,
+ PseudoFunnelActors,
),
):
maxDiff = None
@@ -659,7 +657,7 @@ class TestFunnelUnorderedSteps(ClickhouseTestMixin, APIBaseTest):
def _get_actor_ids_at_step(self, filter, funnel_step, breakdown_value=None):
filter = Filter(data=filter, team=self.team)
person_filter = filter.shallow_clone({"funnel_step": funnel_step, "funnel_step_breakdown": breakdown_value})
- _, serialized_result, _ = ClickhouseFunnelUnorderedActors(person_filter, self.team).get_actors()
+ _, serialized_result, _ = PseudoFunnelActors(person_filter, self.team).get_actors()
return [val["id"] for val in serialized_result]