Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ the release.
([#2503](https://github.com/open-telemetry/opentelemetry-demo/pull/2503))
* [grafana] APM dashboard: Add host metrics per service instance
([#2507](https://github.com/open-telemetry/opentelemetry-demo/pull/2507))
* [react-native-app] Make frontend proxy URL configurable through app settings
([#2531](https://github.com/open-telemetry/opentelemetry-demo/pull/2531))

## 2.0.2

Expand Down
7 changes: 7 additions & 0 deletions src/react-native-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ This will create a `react-native-app.apk` file in the directory where you ran
the command. If you have an Android emulator running on your machine then you
can drag and drop this file onto the emulator's window in order to install it.

## Pointing to another demo environment

By default, the app will point to `EXPO_PUBLIC_FRONTEND_PROXY_PORT` on
localhost to interact with the demo APIs. This can be changed in the Settings
tab when running the app to point to a demo environment running on a
different server.

## Troubleshooting

### JS Bundle: build issues
Expand Down
13 changes: 13 additions & 0 deletions src/react-native-app/app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ export default function TabLayout() {
),
}}
/>
<Tabs.Screen
name="settings"
options={{
title: "Settings",
tabBarShowLabel: false,
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? "settings" : "settings-outline"}
color={color}
/>
),
}}
/>
</Tabs>
);
}
40 changes: 40 additions & 0 deletions src/react-native-app/app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
import { useQueryClient } from '@tanstack/react-query'
import { ThemedView } from "@/components/ThemedView";
import { StyleSheet } from "react-native";
import { getFrontendProxyURL, setFrontendProxyURL } from "@/utils/Settings";
import { setupTracerProvider } from "@/hooks/useTracer";
import { trace } from "@opentelemetry/api";
import { Setting } from "@/components/Setting";

export default function Settings() {
const queryClient = useQueryClient()

const onSetFrontendProxyURL = async (value: string) => {
await setFrontendProxyURL(value);

// Clear any cached queries since we now have a new endpoint to hit for everything
await queryClient.invalidateQueries();

// Need to setup a new tracer provider since the export URL for traces has now changed
trace.disable();
const provider = setupTracerProvider(value);
trace.setGlobalTracerProvider(provider);
};

return (
<ThemedView style={styles.container}>
<Setting name="Frontend Proxy URL" get={getFrontendProxyURL} set={onSetFrontendProxyURL} />
</ThemedView>
);
}

const styles = StyleSheet.create({
container: {
display: "flex",
gap: 20,
paddingLeft: 20,
height: "100%",
},
});
6 changes: 3 additions & 3 deletions src/react-native-app/components/ProductCard/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
import { Product } from "@/protos/demo";
import { ThemedView } from "@/components/ThemedView";
import { useState, useEffect, useMemo } from "react";
import getLocalhost from "@/utils/Localhost";
import { Image, Pressable, StyleSheet } from "react-native";
import { ThemedText } from "@/components/ThemedText";
import { useThemeColor } from "@/hooks/useThemeColor";
import getFrontendProxyURL from "@/utils/Settings";

interface IProps {
product: Product;
onClickAdd: () => void;
}

async function getImageURL(picture: string) {
const localhost = await getLocalhost();
return `http://${localhost}:${process.env.EXPO_PUBLIC_FRONTEND_PROXY_PORT}/images/products/${picture}`;
const proxyURL = await getFrontendProxyURL();
return `${proxyURL}/images/products/${picture}`;
}

const ProductCard = ({
Expand Down
76 changes: 76 additions & 0 deletions src/react-native-app/components/Setting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
import {Pressable, StyleSheet, TextInput, type TextInputProps} from "react-native";
import { ThemedView } from "@/components/ThemedView";
import { ThemedText } from "@/components/ThemedText";
import { useThemeColor } from "@/hooks/useThemeColor";
import {useCallback, useEffect, useState} from "react";
import Toast from "react-native-toast-message";

export type SettingProps = TextInputProps & {
name: string;
get: () => Promise<string>;
set: (value: string) => Promise<void>;
};

export function Setting({ name, get, set, ...otherProps }: SettingProps) {
const color = useThemeColor({}, "text");
const [loading, setLoading] = useState(false);
const [text, setText] = useState('');

useEffect(() => {
get().then(existingValue => {
setText(existingValue);
setLoading(false);
});
}, []);

const onApply = useCallback(async () => {
await set(text);

Toast.show({
type: "success",
position: "bottom",
text1: `${name} applied`,
});
}, [text]);

return (
<ThemedView style={styles.container}>
<ThemedText>{name}:</ThemedText>
{loading ? (
<ThemedText>Fetching current value...</ThemedText>
) : (
<>
<TextInput
style={{ color }}
onChangeText={setText}
value={text}
{...otherProps}
/>
<Pressable style={styles.apply} onPress={onApply}>
<ThemedText style={styles.applyText}>Apply</ThemedText>
</Pressable>
</>
)}
</ThemedView>
);
}


const styles = StyleSheet.create({
container: {
display: "flex",
gap: 5,
},
apply: {
borderRadius: 4,
backgroundColor: "green",
alignItems: "center",
width: 100,
position: "relative",
},
applyText: {
color: "white",
},
});
16 changes: 9 additions & 7 deletions src/react-native-app/hooks/useTracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
ATTR_SERVICE_VERSION,
} from "@opentelemetry/semantic-conventions/incubating";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import getLocalhost from "@/utils/Localhost";
import { useEffect, useState } from "react";
import {
getDeviceId,
Expand All @@ -30,10 +29,9 @@ import {
} from "react-native-device-info";
import { Platform } from "react-native";
import { SessionIdProcessor } from "@/utils/SessionIdProcessor";
import getFrontendProxyURL from "@/utils/Settings";

const Tracer = async () => {
const localhost = await getLocalhost();

export const setupTracerProvider = (proxyURL: string) => {
// TODO Should add a resource detector for React Native that provides this automatically
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: "react-native-app",
Expand All @@ -49,23 +47,27 @@ const Tracer = async () => {
// for React Native.
// Alternatively could offer a TracerProvider that exposed a JS interface on top of the OTEL Android and Swift SDKS,
// giving developers the option of collecting telemetry at the native mobile layer
const provider = new WebTracerProvider({
return new WebTracerProvider({
resource,
spanProcessors: [
new BatchSpanProcessor(
new OTLPTraceExporter({
url: `http://${localhost}:${process.env.EXPO_PUBLIC_FRONTEND_PROXY_PORT}/otlp-http/v1/traces`,
url: `${proxyURL}/otlp-http/v1/traces`,
}),
{
scheduledDelayMillis: 500,
},
),

// TODO introduce a React Native session processor package that could be used here, taking into account mobile
// specific considerations for the session such as putting the app into the background
new SessionIdProcessor(),
],
});
}

const Tracer = async () => {
const proxyURL = await getFrontendProxyURL();
const provider = setupTracerProvider(proxyURL);

provider.register({
propagator: new CompositePropagator({
Expand Down
7 changes: 3 additions & 4 deletions src/react-native-app/utils/Request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/**
* Copied with modification from src/frontend/utils/Request.ts
*/
import getLocalhost from "@/utils/Localhost";
import getFrontendProxyURL from "@/utils/Settings";

interface IRequestParams {
url: string;
Expand All @@ -22,9 +22,8 @@ const request = async <T>({
"content-type": "application/json",
},
}: IRequestParams): Promise<T> => {
const localhost = await getLocalhost();
const API_URL = `http://${localhost}:${process.env.EXPO_PUBLIC_FRONTEND_PROXY_PORT}`;
const requestURL = `${API_URL}${url}?${new URLSearchParams(queryParams).toString()}`;
const proxyURL = await getFrontendProxyURL();
const requestURL = `${proxyURL}${url}?${new URLSearchParams(queryParams).toString()}`;
const requestBody = body ? JSON.stringify(body) : undefined;
const response = await fetch(requestURL, {
method,
Expand Down
22 changes: 22 additions & 0 deletions src/react-native-app/utils/Settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
import AsyncStorage from "@react-native-async-storage/async-storage";
import getLocalhost from "@/utils/Localhost";

const FRONTEND_PROXY_URL_SETTING = 'frontend_proxy_url';

export const getFrontendProxyURL = async (): Promise<string> => {
const proxyURL = await AsyncStorage.getItem(FRONTEND_PROXY_URL_SETTING);
if (proxyURL) {
return proxyURL
} else {
const localhost = await getLocalhost();
return `http://${localhost}:${process.env.EXPO_PUBLIC_FRONTEND_PROXY_PORT}`;
}
};

export const setFrontendProxyURL = async (url: string) => {
await AsyncStorage.setItem(FRONTEND_PROXY_URL_SETTING, url);
}

export default getFrontendProxyURL;
Loading