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]