diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 2d12ca9ea..3baff5a5f 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -32,6 +32,8 @@ dev,react-native-webview,MIT,"Copyright (c) 2015-present, Facebook, Inc." dev,react-test-renderer,MIT,"Copyright (c) Facebook, Inc. and its affiliates." dev,typescript,Apache-2.0,"Copyright Microsoft Corporation" dev,genversion,MIT,"Copyright (c) 2021 Akseli Palén" +dev,@openfeature/core,Apache-2.0,"Copyright (c) The OpenFeature Authors" +prod,@openfeature/web-sdk,Apache-2.0,"Copyright (c) The OpenFeature Authors" prod,chokidar,MIT,"Copyright (c) 2012 Paul Miller (https://paulmillr.com), Elan Shanker" prod,fast-glob,MIT,"Copyright (c) Denis Malinochkin" prod,svgo,MIT,"Copyright (c) Kir Belevich" diff --git a/example-new-architecture/App.tsx b/example-new-architecture/App.tsx index e21a00e73..695640c1e 100644 --- a/example-new-architecture/App.tsx +++ b/example-new-architecture/App.tsx @@ -11,7 +11,13 @@ import { RumConfiguration, DdFlags, } from '@datadog/mobile-react-native'; -import React from 'react'; +import {DatadogProvider} from '@datadog/mobile-react-native-openfeature'; +import { + OpenFeature, + OpenFeatureProvider, + useObjectFlagDetails, +} from '@openfeature/react-sdk'; +import React, {Suspense} from 'react'; import type {PropsWithChildren} from 'react'; import { ActivityIndicator, @@ -35,119 +41,102 @@ import { import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials'; (async () => { - const config = new CoreConfiguration( - CLIENT_TOKEN, - ENVIRONMENT, - ); + const config = new CoreConfiguration(CLIENT_TOKEN, ENVIRONMENT); config.verbosity = SdkVerbosity.DEBUG; config.uploadFrequency = UploadFrequency.FREQUENT; config.batchSize = BatchSize.SMALL; + + // Enable RUM. config.rumConfiguration = new RumConfiguration( APPLICATION_ID, true, true, - true - ) + true, + ); config.rumConfiguration.sessionSampleRate = 100; config.rumConfiguration.telemetrySampleRate = 100; + // Initialize the Datadog SDK. await DdSdkReactNative.initialize(config); + + // Enable Datadog Flags feature. + await DdFlags.enable(); + + // Set the provider with OpenFeature. + const provider = new DatadogProvider(); + OpenFeature.setProvider(provider); + + // Datadog SDK usage examples. await DdRum.startView('main', 'Main'); setTimeout(async () => { await DdRum.addTiming('one_second'); }, 1000); await DdRum.addAction(RumActionType.CUSTOM, 'custom action'); + await DdLogs.info('info log'); + const spanId = await DdTrace.startSpan('test span'); await DdTrace.finishSpan(spanId); })(); -type SectionProps = PropsWithChildren<{ - title: string; -}>; +function AppWithProviders() { + React.useEffect(() => { + const user = { + id: 'user-123', + favoriteFruit: 'apple', + }; + + OpenFeature.setContext({ + targetingKey: user.id, + favoriteFruit: user.favoriteFruit, + }); + }, []); -function Section({children, title}: SectionProps): React.JSX.Element { - const isDarkMode = useColorScheme() === 'dark'; return ( - - - {title} - - - {children} - - + + + + }> + + + + ); } function App(): React.JSX.Element { - const [isInitialized, setIsInitialized] = React.useState(false); - - React.useEffect(() => { - (async () => { - // This is a blocking async app initialization effect. - // It simulates the way most React Native applications are initialized. - await DdFlags.enable(); - const client = DdFlags.getClient(); - - const userId = 'test-user-1'; - const userAttributes = { - country: 'US', - }; - - await client.setEvaluationContext({targetingKey: userId, attributes: userAttributes}); - - setIsInitialized(true); - })().catch(console.error); - }, []); + const greetingFlag = useObjectFlagDetails('rn-sdk-test-json-flag', { + greeting: 'Default greeting', + }); const isDarkMode = useColorScheme() === 'dark'; const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; - if (!isInitialized) { - return ( - - - - ); - } - - // TODO: [FFL-908] Use OpenFeature SDK instead of a manual client call. - const testFlagKey = 'rn-sdk-test-json-flag'; - const testFlag = DdFlags.getClient().getObjectValue(testFlagKey, {greeting: "Default greeting"}); // https://app.datadoghq.com/feature-flags/bcf75cd6-96d8-4182-8871-0b66ad76127a?environmentId=d114cd9a-79ed-4c56-bcf3-bcac9293653b - return ( - +
- -
- Flag value for {testFlagKey} is{'\n'} - {JSON.stringify(testFlag)} + + +
+ The title of this section is based on the{' '} + {greetingFlag.flagKey} feature + flag.{'\n\n'} + If it's different from "Default greeting", then it is coming from + the feature flag evaluation.{'\n\n'} + Inspect greetingFlag in{' '} + App.tsx for more evaluation + details.
+
Edit App.tsx to change this screen and then come back to see your edits. @@ -168,6 +157,36 @@ function App(): React.JSX.Element { ); } +type SectionProps = PropsWithChildren<{ + title: string; +}>; + +function Section({children, title}: SectionProps): React.JSX.Element { + const isDarkMode = useColorScheme() === 'dark'; + return ( + + + {title} + + + {children} + + + ); +} + const styles = StyleSheet.create({ sectionContainer: { marginTop: 32, @@ -187,4 +206,4 @@ const styles = StyleSheet.create({ }, }); -export default App; +export default AppWithProviders; diff --git a/example-new-architecture/package.json b/example-new-architecture/package.json index b72ce69e4..765885691 100644 --- a/example-new-architecture/package.json +++ b/example-new-architecture/package.json @@ -9,6 +9,8 @@ }, "dependencies": { "@datadog/mobile-react-native": "workspace:packages/core", + "@datadog/mobile-react-native-openfeature": "workspace:packages/react-native-openfeature-provider", + "@openfeature/react-sdk": "^1.1.0", "react": "18.3.1", "react-native": "0.76.9" }, diff --git a/example/package.json b/example/package.json index d114dc49a..32fdb083b 100644 --- a/example/package.json +++ b/example/package.json @@ -15,6 +15,7 @@ "@datadog/mobile-react-native-session-replay": "workspace:packages/react-native-session-replay", "@datadog/mobile-react-native-webview": "workspace:packages/react-native-webview", "@datadog/mobile-react-navigation": "workspace:packages/react-navigation", + "@openfeature/react-sdk": "^1.2.0", "@react-native-async-storage/async-storage": "^2.1.2", "@react-native-community/cli": "15.0.1", "@react-native-community/cli-platform-android": "15.0.1", diff --git a/example/src/App.tsx b/example/src/App.tsx index 7a4de6a47..cf5d9e91d 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -7,11 +7,12 @@ import AboutScreen from './screens/AboutScreen'; import style from './screens/styles'; import { navigationRef } from './NavigationRoot'; import { DdRumReactNavigationTracking, ViewNamePredicate } from '@datadog/mobile-react-navigation'; -import {DatadogProvider, DatadogProviderConfiguration, FileBasedConfiguration, RumConfiguration} from '@datadog/mobile-react-native' +import { DatadogProvider, DatadogProviderConfiguration, FileBasedConfiguration, RumConfiguration, DdFlags, TrackingConsent } from '@datadog/mobile-react-native' +import { DatadogProvider as OpenFeatureDatadogProvider } from '@datadog/mobile-react-native-openfeature'; +import { OpenFeature, OpenFeatureProvider } from '@openfeature/react-sdk'; import { Route } from "@react-navigation/native"; import { NestedNavigator } from './screens/NestedNavigator/NestedNavigator'; import { getDatadogConfig, onDatadogInitialization } from './ddUtils'; -import { TrackingConsent } from '@datadog/mobile-react-native'; import { NavigationTrackingOptions, ParamsTrackingPredicate, ViewTrackingPredicate } from '@datadog/mobile-react-navigation/src/rum/instrumentation/DdRumReactNavigationTracking'; const Tab = createBottomTabNavigator(); @@ -21,7 +22,7 @@ const viewNamePredicate: ViewNamePredicate = function customViewNamePredicate(ro return "Custom RN " + trackedName; } -const viewTrackingPredicate: ViewTrackingPredicate = function customViewTrackingPredicate(route: Route) { +const viewTrackingPredicate: ViewTrackingPredicate = function customViewTrackingPredicate(route: Route) { if (route.name === "AlertModal") { return false; } @@ -29,7 +30,7 @@ const viewTrackingPredicate: ViewTrackingPredicate = function customViewTracking return true; } -const paramsTrackingPredicate: ParamsTrackingPredicate = function customParamsTrackingPredicate(route: Route) { +const paramsTrackingPredicate: ParamsTrackingPredicate = function customParamsTrackingPredicate(route: Route) { const filteredParams: any = {}; if (route.params?.creditCardNumber) { filteredParams["creditCardNumber"] = "XXXX XXXX XXXX XXXX"; @@ -57,9 +58,9 @@ const configuration = getDatadogConfig(TrackingConsent.GRANTED) // 3.- File based configuration from .json and custom mapper setup // const configuration = new FileBasedConfiguration( { -// configuration: require("../datadog-configuration.json").configuration, -// errorEventMapper: (event) => event, -// resourceEventMapper: (event) => event, +// configuration: require("../datadog-configuration.json").configuration, +// errorEventMapper: (event) => event, +// resourceEventMapper: (event) => event, // actionEventMapper: (event) => event}); // 4.- File based configuration from the native side (using initFromNative) @@ -68,26 +69,38 @@ const configuration = getDatadogConfig(TrackingConsent.GRANTED) // const configuration = new DatadogProviderConfiguration("fake_value", "fake_value"); // configuration.rumConfiguration = new RumConfiguration("fake_value") -export default function App() { +const handleDatadogInitialization = async () => { + onDatadogInitialization(); + + // Enable Datadog Flags feature. + await DdFlags.enable(); + // Set the provider with OpenFeature. + const provider = new OpenFeatureDatadogProvider(); + OpenFeature.setProvider(provider); +} + +export default function App() { return ( - - { - DdRumReactNavigationTracking.startTrackingViews( - navigationRef.current, - navigationTrackingOptions) - }}> - null + + + { + DdRumReactNavigationTracking.startTrackingViews( + navigationRef.current, + navigationTrackingOptions) }}> - - - - - - + null + }}> + + + + + + + ) } diff --git a/example/src/WixApp.tsx b/example/src/WixApp.tsx index 8664c3e3d..af685b934 100644 --- a/example/src/WixApp.tsx +++ b/example/src/WixApp.tsx @@ -14,6 +14,7 @@ import styles from './screens/styles'; import { DdFlags } from '@datadog/mobile-react-native'; import TraceScreen from './screens/TraceScreen'; import { NavigationTrackingOptions, ParamsTrackingPredicate, ViewTrackingPredicate } from '@datadog/mobile-react-native-navigation/src/rum/instrumentation/DdRumReactNativeNavigationTracking'; +import { OpenFeatureProvider, useFlag } from '@openfeature/react-sdk'; // === Navigation Tracking custom predicates const viewNamePredicate: ViewNamePredicate = function customViewNamePredicate(_event: ComponentDidAppearEvent, trackedName: string) { @@ -62,45 +63,24 @@ function startReactNativeNavigation() { } function registerScreens() { - Navigation.registerComponent('Home', () => HomeScreen); + Navigation.registerComponent('Home', () => HomeScreenWithProviders); Navigation.registerComponent('Main', () => MainScreen); Navigation.registerComponent('Error', () => ErrorScreen); Navigation.registerComponent('Trace', () => TraceScreen); Navigation.registerComponent('About', () => AboutScreen); } -const HomeScreen = props => { - const [isInitialized, setIsInitialized] = React.useState(false); - - React.useEffect(() => { - (async () => { - // This is a blocking async app initialization effect. - // It simulates the way most React Native applications are initialized. - await DdFlags.enable(); - const client = DdFlags.getClient(); - - const userId = 'test-user-1'; - const userAttributes = { - country: 'US', - }; - - await client.setEvaluationContext({targetingKey: userId, attributes: userAttributes}); - - setIsInitialized(true); - })().catch(console.error); - }, []); - - if (!isInitialized) { - return ( - - - - ) - } +const HomeScreenWithProviders = () => { + return ( + + + + ) +} - // TODO: [FFL-908] Use OpenFeature SDK instead of a manual client call. +const HomeScreen = props => { const testFlagKey = 'rn-sdk-test-json-flag'; - const testFlag = DdFlags.getClient().getObjectValue(testFlagKey, {greeting: "Default greeting"}); // https://app.datadoghq.com/feature-flags/bcf75cd6-96d8-4182-8871-0b66ad76127a?environmentId=d114cd9a-79ed-4c56-bcf3-bcac9293653b + const flag = useFlag(testFlagKey, {greeting: "Default greeting"}); return ( @@ -147,7 +127,7 @@ const HomeScreen = props => { }); }} /> - {testFlagKey}: {JSON.stringify(testFlag)} + {testFlagKey}: {JSON.stringify(flag.value)} ); }; diff --git a/example/src/ddUtils.tsx b/example/src/ddUtils.tsx index ad6702a4a..5c0c38e73 100644 --- a/example/src/ddUtils.tsx +++ b/example/src/ddUtils.tsx @@ -8,6 +8,8 @@ import { TrackingConsent, DdFlags, } from '@datadog/mobile-react-native'; +import { DatadogProvider as OpenFeatureDatadogProvider } from '@datadog/mobile-react-native-openfeature'; +import { OpenFeature } from '@openfeature/react-sdk'; import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials'; @@ -53,5 +55,10 @@ export function initializeDatadog(trackingConsent: TrackingConsent) { DdSdkReactNative.addAttributes({campaign: "ad-network"}) }); - DdFlags.enable() + // Enable the Flags feature. + DdFlags.enable().then(() => { + // Set the provider with OpenFeature. + const provider = new OpenFeatureDatadogProvider(); + OpenFeature.setProvider(provider); + }) } diff --git a/example/src/screens/MainScreen.tsx b/example/src/screens/MainScreen.tsx index ebf0360bc..bf9737254 100644 --- a/example/src/screens/MainScreen.tsx +++ b/example/src/screens/MainScreen.tsx @@ -9,9 +9,10 @@ import { View, Text, Button, TouchableOpacity, TouchableWithoutFeedback, TouchableNativeFeedback, ActivityIndicator } from 'react-native'; +import { DdLogs, DdSdkReactNative, TrackingConsent, DdFlags } from '@datadog/mobile-react-native'; +import { FeatureFlag } from '@openfeature/react-sdk'; import styles from './styles'; import { APPLICATION_KEY, API_KEY } from '../../src/ddCredentials'; -import { DdLogs, DdSdkReactNative, TrackingConsent, DdFlags } from '@datadog/mobile-react-native'; import { getTrackingConsent, saveTrackingConsent } from '../utils'; import { ConsentModal } from '../components/consent'; import { DdRum } from '../../../packages/core/src/rum/DdRum'; @@ -27,7 +28,6 @@ interface MainScreenState { resultTouchableNativeFeedback: string, trackingConsent: TrackingConsent, trackingConsentModalVisible: boolean - flagsInitialized: boolean } export default class MainScreen extends Component { @@ -41,8 +41,7 @@ export default class MainScreen extends Component { resultButtonAction: "", resultTouchableOpacityAction: "", trackingConsent: TrackingConsent.PENDING, - trackingConsentModalVisible: false, - flagsInitialized: false + trackingConsentModalVisible: false } as MainScreenState; this.consentModal = React.createRef() } @@ -96,7 +95,6 @@ export default class MainScreen extends Component { componentDidMount() { this.updateTrackingConsent() - this.initializeFlags(); DdLogs.debug("[DATADOG SDK] Test React Native Debug Log"); } @@ -108,24 +106,6 @@ export default class MainScreen extends Component { }) } - initializeFlags() { - (async () => { - // This is a blocking async app initialization effect. - // It simulates the way most React Native applications are initialized. - await DdFlags.enable(); - const client = DdFlags.getClient(); - - const userId = 'test-user-1'; - const userAttributes = { - country: 'US', - }; - - await client.setEvaluationContext({targetingKey: userId, attributes: userAttributes}); - - this.setState({ flagsInitialized: true }) - })(); - } - setTrackingConsentModalVisible(visible: boolean) { if (visible) { this.consentModal.current.setConsent(this.state.trackingConsent) @@ -134,18 +114,13 @@ export default class MainScreen extends Component { } render() { - if (!this.state.flagsInitialized) { - return - - - } + return + Welcome!}> + Greetings from the Feature Flags! + - // TODO: [FFL-908] Use OpenFeature SDK instead of a manual client call. - const testFlagKey = 'rn-sdk-test-json-flag'; - const testFlag = DdFlags.getClient().getObjectValue(testFlagKey, {greeting: "Default greeting"}); // https://app.datadoghq.com/feature-flags/bcf75cd6-96d8-4182-8871-0b66ad76127a?environmentId=d114cd9a-79ed-4c56-bcf3-bcac9293653b + The above greeting is being controlled by the{'\n'}`rn-sdk-test-boolean-flag` feature flag. - return - {this.state.welcomeMessage}