Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24.x'
Expand Down
2 changes: 2 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ include:

variables:
GIT_DEPTH: 5
GIT_SUBMODULE_STRATEGY: recursive
DEVELOP_BRANCH: 'develop'
ANDROID_SDK_VERSION: 'commandlinetools-mac-11076708_latest'
EMULATOR_NAME: 'android_emulator'
Expand Down Expand Up @@ -92,6 +93,7 @@ test:native-android:
- (cd packages/core/android && ./gradlew build -PDdSdkReactNative_minSdkVersion=24)
- (cd packages/react-native-session-replay/android && ./gradlew build -PDdSdkReactNative_minSdkVersion=24 -PDatadogSDKReactNativeSessionReplay_minSdkVersion=24)
- (cd packages/internal-testing-tools/android && ./gradlew build -PDdSdkReactNative_minSdkVersion=24 -PDatadogInternalTesting_minSdkVersion=24)
- ./example-new-architecture/scripts/native-ffe-offline-android-smoke.sh

test:native-ios:
tags: ['macos:sonoma', 'specific:true']
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "packages/core/src/flags/__fixtures__/ffe-system-test-data"]
path = packages/core/src/flags/__fixtures__/ffe-system-test-data
url = https://github.com/DataDog/ffe-system-test-data.git
1 change: 1 addition & 0 deletions example-new-architecture/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ yarn-error.log

# Secrets
ddCredentials.*
!ddCredentials.example.js

# Yarn ignored files
.pnp.*
Expand Down
238 changes: 167 additions & 71 deletions example-new-architecture/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,12 @@ import {
DdLogs,
DdTrace,
TrackingConsent,
DdFlags,
PropagatorType,
} from '@datadog/mobile-react-native';
import {DatadogOpenFeatureProvider} from '@datadog/mobile-react-native-openfeature';
import {
OpenFeature,
OpenFeatureProvider,
useObjectFlagDetails,
} from '@openfeature/react-sdk';
import React, {Suspense} from 'react';
import React from 'react';
import type {PropsWithChildren} from 'react';
import {
ActivityIndicator,
Pressable,
SafeAreaView,
ScrollView,
StatusBar,
Expand All @@ -38,10 +31,14 @@ import {
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
// @ts-ignore
import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
import * as ddCredentials from './ddCredentials.example';
import {runNativeFfeOfflineFixtureCorpus} from './nativeFfeOfflineFixtureRunner';

const APPLICATION_ID = ddCredentials.APPLICATION_ID;
const CLIENT_TOKEN = ddCredentials.CLIENT_TOKEN;
const ENVIRONMENT = ddCredentials.ENVIRONMENT;

(async () => {
const datadogInitialization = (async () => {
const config = new CoreConfiguration(
CLIENT_TOKEN,
ENVIRONMENT,
Expand All @@ -56,14 +53,19 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
sessionSampleRate: 100,
telemetrySampleRate: 100,
nativeCrashReportEnabled: true,
firstPartyHosts: [{
match: "example.com",
propagatorTypes: [PropagatorType.B3MULTI, PropagatorType.TRACECONTEXT]
}]
firstPartyHosts: [
{
match: 'example.com',
propagatorTypes: [
PropagatorType.B3MULTI,
PropagatorType.TRACECONTEXT,
],
},
],
},
logsConfiguration: {},
traceConfiguration: {}
}
traceConfiguration: {},
},
);
config.verbosity = SdkVerbosity.DEBUG;
config.uploadFrequency = UploadFrequency.FREQUENT;
Expand All @@ -72,13 +74,6 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
// Initialize the Datadog SDK.
await DdSdkReactNative.initialize(config);

// Enable Datadog Flags feature.
await DdFlags.enable();

// Set the provider with OpenFeature.
const provider = new DatadogOpenFeatureProvider();
OpenFeature.setProvider(provider);

// Datadog SDK usage examples.
await DdRum.startView('main', 'Main');
setTimeout(async () => {
Expand All @@ -92,38 +87,7 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
await DdTrace.finishSpan(spanId);
})();

function AppWithProviders() {
React.useEffect(() => {
const user = {
id: 'user-123',
favoriteFruit: 'apple',
};

OpenFeature.setContext({
targetingKey: user.id,
favoriteFruit: user.favoriteFruit,
});
}, []);

return (
<Suspense
fallback={
<SafeAreaView style={{height: '100%', justifyContent: 'center'}}>
<ActivityIndicator />
</SafeAreaView>
}>
<OpenFeatureProvider suspendUntilReady>
<App />
</OpenFeatureProvider>
</Suspense>
);
}

function App(): React.JSX.Element {
const greetingFlag = useObjectFlagDetails('rn-sdk-test-json-flag', {
greeting: 'Default greeting',
});

const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
Expand All @@ -135,20 +99,16 @@ function App(): React.JSX.Element {
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}
>
<Header />

<View style={{backgroundColor: isDarkMode ? Colors.black : Colors.white}}>
<Section title={greetingFlag.value.greeting}>
The title of this section is based on the{' '}
<Text style={styles.highlight}>{greetingFlag.flagKey}</Text> feature
flag.{'\n\n'}
If it's different from "Default greeting", then it is coming from
the feature flag evaluation.{'\n\n'}
Evaluation reason is <Text style={styles.highlight}>{greetingFlag.reason}</Text>.{'\n\n'}Inspect <Text style={styles.highlight}>greetingFlag</Text> in{' '}
<Text style={styles.highlight}>App.tsx</Text> for more evaluation
details.
</Section>
<View
style={{backgroundColor: isDarkMode ? Colors.black : Colors.white}}
>
<NativeFfeFlowPanel isDarkMode={isDarkMode} />

<Section title="Step One">
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
Expand All @@ -170,6 +130,111 @@ function App(): React.JSX.Element {
);
}

type NativeFfeFlowState = {
status: 'idle' | 'loading' | 'ready' | 'error';
summary: string;
details?: string;
};

function NativeFfeFlowPanel({
isDarkMode,
}: {
isDarkMode: boolean;
}): React.JSX.Element {
const [flowState, setFlowState] = React.useState<NativeFfeFlowState>({
status: 'idle',
summary: 'Native FF&E flow has not run yet.',
});
const loading = flowState.status === 'loading';

const runNativeFfeFlow = React.useCallback(async () => {
setFlowState({
status: 'loading',
summary: 'Running native FF&E offline fixture corpus...',
});

try {
await datadogInitialization;

const report = await runNativeFfeOfflineFixtureCorpus();

setFlowState({
status: 'ready',
summary: report.summary,
details: JSON.stringify(report.details, null, 2),
});
} catch (error) {
setFlowState({
status: 'error',
summary:
error instanceof Error ? error.message : 'Native FF&E flow failed.',
});
}
}, []);

const autoRunStarted = React.useRef(false);
React.useEffect(() => {
if (autoRunStarted.current) {
return;
}
autoRunStarted.current = true;
void runNativeFfeFlow();
}, [runNativeFfeFlow]);

return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}
>
Native FFE flow
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}
>
{flowState.summary}
</Text>
<Pressable
accessibilityRole="button"
disabled={loading}
onPress={runNativeFfeFlow}
style={({pressed}) => [
styles.nativeFfeButton,
loading && styles.nativeFfeButtonDisabled,
pressed && !loading && styles.nativeFfeButtonPressed,
]}
>
<Text style={styles.nativeFfeButtonText}>
{loading ? 'Running...' : 'Run offline fixture corpus'}
</Text>
</Pressable>
{flowState.details ? (
<Text
selectable
style={[
styles.nativeFfeDetails,
{
color: isDarkMode ? Colors.light : Colors.dark,
backgroundColor: isDarkMode ? Colors.black : '#f3f4f6',
},
]}
>
{flowState.details}
</Text>
) : null}
</View>
);
}

type SectionProps = PropsWithChildren<{
title: string;
}>;
Expand All @@ -184,7 +249,8 @@ function Section({children, title}: SectionProps): React.JSX.Element {
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
]}
>
{title}
</Text>
<Text
Expand All @@ -193,7 +259,8 @@ function Section({children, title}: SectionProps): React.JSX.Element {
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
]}
>
{children}
</Text>
</View>
Expand All @@ -217,6 +284,35 @@ const styles = StyleSheet.create({
highlight: {
fontWeight: '700',
},
nativeFfeButton: {
alignItems: 'center',
alignSelf: 'flex-start',
backgroundColor: '#2563eb',
borderRadius: 6,
marginTop: 16,
minHeight: 44,
justifyContent: 'center',
paddingHorizontal: 16,
},
nativeFfeButtonDisabled: {
opacity: 0.6,
},
nativeFfeButtonPressed: {
backgroundColor: '#1d4ed8',
},
nativeFfeButtonText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
},
nativeFfeDetails: {
borderRadius: 6,
fontFamily: 'Menlo',
fontSize: 12,
lineHeight: 16,
marginTop: 12,
padding: 12,
},
});

export default AppWithProviders;
export default App;
32 changes: 32 additions & 0 deletions example-new-architecture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# New Architecture Example

## Datadog Credentials

The example imports `ddCredentials.example.js`, which contains fake placeholder
values. That is enough for the offline native FF&E fixture demo and for CI,
because the demo passes a bundled rules configuration JSON file through the
React Native bridge and does not make a feature flag network request.

To point the example at a real staging app for manual SDK/RUM validation, edit
the values in `ddCredentials.example.js` locally:

- `CLIENT_TOKEN`: Datadog public client token for SDK initialization.
- `APPLICATION_ID`: RUM application ID. The placeholder keeps the native FF&E demo runnable; use a real staging RUM application ID to validate RUM flag annotation.
- `ENVIRONMENT`: use `staging` for this demo.

Do not commit real credentials.

## Native FFE Offline Smoke

CI runs the Android smoke test below to exercise the offline fixture corpus from
React Native JS through the native bridge:

```sh
ANDROID_HOME="$ANDROID_HOME" ANDROID_SDK_ROOT="$ANDROID_SDK_ROOT" \
./example-new-architecture/scripts/native-ffe-offline-android-smoke.sh
```

The smoke test starts Metro if needed, installs the new-architecture Android
app, launches it on an emulator, and waits for `Native FFE offline fixture pass:
233 cases across 30 files`. It does not use credentials or make a feature flag
network request.
Loading